x-tools /
xtools
This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include, or for example
via PHP's auto-loading mechanism.
| 1 | <?php |
||
| 2 | |||
| 3 | declare(strict_types = 1); |
||
| 4 | |||
| 5 | namespace App\Repository; |
||
| 6 | |||
| 7 | use App\Helper\AutomatedEditsHelper; |
||
| 8 | use App\Model\Edit; |
||
| 9 | use App\Model\Project; |
||
| 10 | use App\Model\User; |
||
| 11 | use Doctrine\DBAL\Connection; |
||
| 12 | use Doctrine\DBAL\Driver\ResultStatement; |
||
| 13 | use Doctrine\DBAL\ParameterType; |
||
| 14 | use Doctrine\Persistence\ManagerRegistry; |
||
| 15 | use GuzzleHttp\Client; |
||
| 16 | use Psr\Cache\CacheItemPoolInterface; |
||
| 17 | use Psr\Log\LoggerInterface; |
||
| 18 | use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; |
||
| 19 | use Wikimedia\IPUtils; |
||
| 20 | |||
| 21 | /** |
||
| 22 | * CategoryEditsRepository is responsible for retrieving data from the database |
||
| 23 | * about the edits made by a user to pages in a set of given categories. |
||
| 24 | * @codeCoverageIgnore |
||
| 25 | */ |
||
| 26 | class CategoryEditsRepository extends Repository |
||
| 27 | { |
||
| 28 | protected AutomatedEditsHelper $autoEditsHelper; |
||
| 29 | protected EditRepository $editRepo; |
||
| 30 | protected PageRepository $pageRepo; |
||
| 31 | protected UserRepository $userRepo; |
||
| 32 | |||
| 33 | /** |
||
| 34 | * @param ManagerRegistry $managerRegistry |
||
| 35 | * @param CacheItemPoolInterface $cache |
||
| 36 | * @param Client $guzzle |
||
| 37 | * @param LoggerInterface $logger |
||
| 38 | * @param ParameterBagInterface $parameterBag |
||
| 39 | * @param bool $isWMF |
||
| 40 | * @param int $queryTimeout |
||
| 41 | * @param AutomatedEditsHelper $autoEditsHelper |
||
| 42 | * @param EditRepository $editRepo |
||
| 43 | * @param PageRepository $pageRepo |
||
| 44 | * @param UserRepository $userRepo |
||
| 45 | */ |
||
| 46 | public function __construct( |
||
| 47 | ManagerRegistry $managerRegistry, |
||
| 48 | CacheItemPoolInterface $cache, |
||
| 49 | Client $guzzle, |
||
| 50 | LoggerInterface $logger, |
||
| 51 | ParameterBagInterface $parameterBag, |
||
| 52 | bool $isWMF, |
||
| 53 | int $queryTimeout, |
||
| 54 | AutomatedEditsHelper $autoEditsHelper, |
||
| 55 | EditRepository $editRepo, |
||
| 56 | PageRepository $pageRepo, |
||
| 57 | UserRepository $userRepo |
||
| 58 | ) { |
||
| 59 | $this->autoEditsHelper = $autoEditsHelper; |
||
| 60 | $this->editRepo = $editRepo; |
||
| 61 | $this->pageRepo = $pageRepo; |
||
| 62 | $this->userRepo = $userRepo; |
||
| 63 | parent::__construct($managerRegistry, $cache, $guzzle, $logger, $parameterBag, $isWMF, $queryTimeout); |
||
| 64 | } |
||
| 65 | |||
| 66 | /** |
||
| 67 | * Get the number of edits this user made to the given categories. |
||
| 68 | * @param Project $project |
||
| 69 | * @param User $user |
||
| 70 | * @param string[] $categories |
||
| 71 | * @param int|false $start Start date as Unix timestamp. |
||
| 72 | * @param int|false $end End date as Unix timestamp. |
||
| 73 | * @return int Result of query, see below. |
||
| 74 | */ |
||
| 75 | public function countCategoryEdits( |
||
| 76 | Project $project, |
||
| 77 | User $user, |
||
| 78 | array $categories, |
||
| 79 | $start = false, |
||
| 80 | $end = false |
||
| 81 | ): int { |
||
| 82 | $cacheKey = $this->getCacheKey(func_get_args(), 'user_categoryeditcount'); |
||
| 83 | if ($this->cache->hasItem($cacheKey)) { |
||
| 84 | return $this->cache->getItem($cacheKey)->get(); |
||
| 85 | } |
||
| 86 | |||
| 87 | $revisionTable = $project->getTableName('revision'); |
||
| 88 | $categorylinksTable = $project->getTableName('categorylinks'); |
||
| 89 | $revDateConditions = $this->getDateConditions($start, $end, false, 'revs.'); |
||
| 90 | $whereClause = 'revs.rev_actor = ?'; |
||
| 91 | $ipcJoin = ''; |
||
| 92 | |||
| 93 | if ($user->isIpRange()) { |
||
| 94 | $ipcTable = $project->getTableName('ip_changes'); |
||
| 95 | $ipcJoin = "JOIN $ipcTable ON ipc_rev_id = revs.rev_id"; |
||
| 96 | $whereClause = 'ipc_hex BETWEEN ? AND ?'; |
||
| 97 | } |
||
| 98 | |||
| 99 | $sql = "SELECT COUNT(DISTINCT revs.rev_id) |
||
| 100 | FROM $revisionTable revs |
||
| 101 | $ipcJoin |
||
| 102 | JOIN $categorylinksTable ON cl_from = rev_page |
||
| 103 | WHERE $whereClause |
||
| 104 | AND cl_to IN (?) |
||
| 105 | $revDateConditions"; |
||
| 106 | $result = (int)$this->executeStmt($sql, $project, $user, $categories)->fetchOne(); |
||
| 107 | |||
| 108 | // Cache and return. |
||
| 109 | return $this->setCache($cacheKey, $result); |
||
| 110 | } |
||
| 111 | |||
| 112 | /** |
||
| 113 | * Get number of edits within each individual category. |
||
| 114 | * @param Project $project |
||
| 115 | * @param User $user |
||
| 116 | * @param array $categories |
||
| 117 | * @param int|false $start |
||
| 118 | * @param int|false $end |
||
| 119 | * @return string[] With categories as keys, counts as values. |
||
| 120 | */ |
||
| 121 | public function getCategoryCounts( |
||
| 122 | Project $project, |
||
| 123 | User $user, |
||
| 124 | array $categories, |
||
| 125 | $start = false, |
||
| 126 | $end = false |
||
| 127 | ): array { |
||
| 128 | $cacheKey = $this->getCacheKey(func_get_args(), 'user_categorycounts'); |
||
| 129 | if ($this->cache->hasItem($cacheKey)) { |
||
| 130 | return $this->cache->getItem($cacheKey)->get(); |
||
| 131 | } |
||
| 132 | |||
| 133 | $revisionTable = $project->getTableName('revision'); |
||
| 134 | $categorylinksTable = $project->getTableName('categorylinks'); |
||
| 135 | $revDateConditions = $this->getDateConditions($start, $end, false, 'revs.'); |
||
| 136 | $whereClause = 'revs.rev_actor = ?'; |
||
| 137 | $ipcJoin = ''; |
||
| 138 | |||
| 139 | if ($user->isIpRange()) { |
||
| 140 | $ipcTable = $project->getTableName('ip_changes'); |
||
| 141 | $ipcJoin = "JOIN $ipcTable ON ipc_rev_id = revs.rev_id"; |
||
| 142 | $whereClause = 'ipc_hex BETWEEN ? AND ?'; |
||
| 143 | } |
||
| 144 | |||
| 145 | $sql = "SELECT cl_to AS cat, COUNT(rev_id) AS edit_count, COUNT(DISTINCT rev_page) AS page_count |
||
| 146 | FROM $revisionTable revs |
||
| 147 | $ipcJoin |
||
| 148 | JOIN $categorylinksTable ON cl_from = rev_page |
||
| 149 | WHERE $whereClause |
||
| 150 | AND cl_to IN (?) |
||
| 151 | $revDateConditions |
||
| 152 | GROUP BY cl_to |
||
| 153 | ORDER BY edit_count DESC"; |
||
| 154 | |||
| 155 | $counts = []; |
||
| 156 | $stmt = $this->executeStmt($sql, $project, $user, $categories); |
||
| 157 | while ($result = $stmt->fetchAssociative()) { |
||
| 158 | $counts[$result['cat']] = [ |
||
| 159 | 'editCount' => (int)$result['edit_count'], |
||
| 160 | 'pageCount' => (int)$result['page_count'], |
||
| 161 | ]; |
||
| 162 | } |
||
| 163 | |||
| 164 | // Cache and return. |
||
| 165 | return $this->setCache($cacheKey, $counts); |
||
| 166 | } |
||
| 167 | |||
| 168 | /** |
||
| 169 | * Get contributions made to the given categories. |
||
| 170 | * @param Project $project |
||
| 171 | * @param User $user |
||
| 172 | * @param string[] $categories |
||
| 173 | * @param int|false $start Start date as Unix timestamp. |
||
| 174 | * @param int|false $end End date as Unix timestamp. |
||
| 175 | * @param false|int $offset Unix timestamp. Used for pagination. |
||
| 176 | * @return string[] Result of query, with columns 'page_title', 'namespace', 'rev_id', 'timestamp', 'minor', |
||
| 177 | * 'length', 'length_change', 'comment' |
||
| 178 | */ |
||
| 179 | public function getCategoryEdits( |
||
| 180 | Project $project, |
||
| 181 | User $user, |
||
| 182 | array $categories, |
||
| 183 | $start = false, |
||
| 184 | $end = false, |
||
| 185 | $offset = false |
||
| 186 | ): array { |
||
| 187 | $cacheKey = $this->getCacheKey(func_get_args(), 'user_categoryedits'); |
||
| 188 | if ($this->cache->hasItem($cacheKey)) { |
||
| 189 | return $this->cache->getItem($cacheKey)->get(); |
||
| 190 | } |
||
| 191 | |||
| 192 | $pageTable = $project->getTableName('page'); |
||
| 193 | $revisionTable = $project->getTableName('revision'); |
||
| 194 | $commentTable = $project->getTableName('comment'); |
||
| 195 | $categorylinksTable = $project->getTableName('categorylinks'); |
||
| 196 | $revDateConditions = $this->getDateConditions($start, $end, $offset, 'revs.'); |
||
| 197 | $whereClause = 'revs.rev_actor = ?'; |
||
| 198 | $ipcJoin = ''; |
||
| 199 | |||
| 200 | if ($user->isIpRange()) { |
||
| 201 | $ipcTable = $project->getTableName('ip_changes'); |
||
| 202 | $ipcJoin = "JOIN $ipcTable ON ipc_rev_id = revs.rev_id"; |
||
| 203 | $whereClause = 'ipc_hex BETWEEN ? AND ?'; |
||
| 204 | } |
||
| 205 | |||
| 206 | $sql = "SELECT page_title, page_namespace AS `namespace`, revs.rev_id AS `rev_id`, |
||
| 207 | revs.rev_timestamp AS `timestamp`, revs.rev_minor_edit AS `minor`, revs.rev_len AS `length`, |
||
| 208 | (CAST(revs.rev_len AS SIGNED) - IFNULL(parentrevs.rev_len, 0)) AS `length_change`, |
||
| 209 | comment_text AS `comment` |
||
| 210 | FROM $pageTable |
||
| 211 | JOIN $revisionTable revs ON page_id = revs.rev_page |
||
| 212 | $ipcJoin |
||
| 213 | JOIN $categorylinksTable ON cl_from = rev_page |
||
| 214 | LEFT JOIN $commentTable comment ON revs.rev_comment_id = comment_id |
||
| 215 | LEFT JOIN $revisionTable parentrevs ON revs.rev_parent_id = parentrevs.rev_id |
||
| 216 | WHERE $whereClause |
||
| 217 | AND cl_to IN (?) |
||
| 218 | $revDateConditions |
||
| 219 | GROUP BY revs.rev_id |
||
| 220 | ORDER BY revs.rev_timestamp DESC |
||
| 221 | LIMIT 50"; |
||
| 222 | |||
| 223 | $result = $this->executeStmt($sql, $project, $user, $categories)->fetchAllAssociative(); |
||
| 224 | |||
| 225 | // Cache and return. |
||
| 226 | return $this->setCache($cacheKey, $result); |
||
| 227 | } |
||
| 228 | |||
| 229 | /** |
||
| 230 | * Bind dates, username and categories then execute the query. |
||
| 231 | * @param string $sql |
||
| 232 | * @param Project $project |
||
| 233 | * @param User $user |
||
| 234 | * @param string[] $categories |
||
| 235 | * @return ResultStatement |
||
| 236 | */ |
||
| 237 | private function executeStmt( |
||
| 238 | string $sql, |
||
| 239 | Project $project, |
||
| 240 | User $user, |
||
| 241 | array $categories |
||
| 242 | ): ResultStatement { |
||
| 243 | if ($user->isIpRange()) { |
||
| 244 | [$hexStart, $hexEnd] = IPUtils::parseRange($user->getUsername()); |
||
| 245 | $params = [ |
||
| 246 | $hexStart, |
||
| 247 | $hexEnd, |
||
| 248 | $categories, |
||
| 249 | ]; |
||
| 250 | $types = [ |
||
| 251 | ParameterType::STRING, |
||
| 252 | ParameterType::STRING, |
||
| 253 | Connection::PARAM_STR_ARRAY, |
||
| 254 | ]; |
||
| 255 | } else { |
||
| 256 | $params = [ |
||
| 257 | $user->getActorId($project), |
||
| 258 | $categories, |
||
| 259 | ]; |
||
| 260 | $types = [ |
||
| 261 | ParameterType::STRING, |
||
| 262 | Connection::PARAM_STR_ARRAY, |
||
| 263 | ]; |
||
| 264 | } |
||
| 265 | |||
| 266 | return $this->getProjectsConnection($project) |
||
| 267 | ->executeQuery($sql, $params, $types); |
||
|
0 ignored issues
–
show
|
|||
| 268 | } |
||
| 269 | |||
| 270 | /** |
||
| 271 | * Get Edits given revision rows (JOINed on the page table). |
||
| 272 | * @param Project $project |
||
| 273 | * @param User $user |
||
| 274 | * @param array $revs Each must contain 'page_title' and 'namespace'. |
||
| 275 | * @return Edit[] |
||
| 276 | */ |
||
| 277 | public function getEditsFromRevs(Project $project, User $user, array $revs): array |
||
| 278 | { |
||
| 279 | return Edit::getEditsFromRevs( |
||
| 280 | $this->pageRepo, |
||
| 281 | $this->editRepo, |
||
| 282 | $this->userRepo, |
||
| 283 | $project, |
||
| 284 | $user, |
||
| 285 | $revs |
||
| 286 | ); |
||
| 287 | } |
||
| 288 | } |
||
| 289 |
$paramscan contain request data and is used in sql context(s) leading to a potential security vulnerability.1 path for user data to reach this point
in vendor/symfony/http-foundation/ParameterBag.php on line 90
$this->attributes->get($key, $this)is assigned to$resultin vendor/symfony/http-foundation/Request.php on line 711
$resultis returnedin vendor/symfony/http-foundation/Request.php on line 712
explode(), andexplode('|', $this->request->get('categories'))is assigned to$categoriesin src/Controller/CategoryEditsController.php on line 109
array_map(), andarray_map(function(...) { /* ... */ }, $categories)is assigned to property CategoryEditsController::$categoriesin src/Controller/CategoryEditsController.php on line 117
in src/Controller/CategoryEditsController.php on line 86
$categoriesin src/Model/CategoryEdits.php on line 43
array_map(), andarray_map(function(...) { /* ... */ }, $categories)is assigned to property CategoryEdits::$categoriesin src/Model/CategoryEdits.php on line 51
in src/Model/CategoryEdits.php on line 198
$categoriesin src/Repository/CategoryEditsRepository.php on line 124
in src/Repository/CategoryEditsRepository.php on line 156
$categoriesin src/Repository/CategoryEditsRepository.php on line 241
array($hexStart, $hexEnd, $categories)is assigned to$paramsin src/Repository/CategoryEditsRepository.php on line 245
Used in sql context
in src/Repository/CategoryEditsRepository.php on line 267
$paramsin vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php on line 1284
expandListParameters()in vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php on line 1299
Doctrine\DBAL\SQLParserUtils::expandListParameters($sql, $params, $types)is assigned to$paramsin vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php on line 1299
in vendor/doctrine/dbal/lib/Doctrine/DBAL/Connection.php on line 1306
$paramsin vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php on line 216
db2_execute()is calledin vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/IBMDB2/DB2Statement.php on line 238
Preventing SQL Injection
There are two options to prevent SQL injection. Generally, it is recommended to use parameter binding:
$stmt = mysqli_prepare("SELECT * FROM users WHERE name = ?"); $stmt->bind_param("s", $taintedUserName);An alternative – although generally not recommended – is to escape your data manually:
$mysqli = new mysqli('localhost', 'user', 'pass', 'dbname'); $escaped = $mysqli->real_escape_string($taintedUserName); $mysqli->query("SELECT * FROM users WHERE name = '".$escaped."'");General Strategies to prevent injection
In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:
if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) { throw new \InvalidArgumentException('This input is not allowed.'); }For numeric data, we recommend to explicitly cast the data: