Issues (438)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

classes/Api.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/* zKillboard
3
 * Copyright (C) 2012-2015 EVE-KILL Team and EVSCO.
4
 *
5
 * This program is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18
19
/**
20
 * Various API helper functions for the website
21
 */
22
class Api
23
{
24
25
	/**
26
	 * Checks a key for validity and KillLog access.
27
	 *
28
	 * @static
29
	 * @param $keyID int The keyID to be checked.
30
	 * @param $vCode string The vCode to be checked
31
	 * @return string A message, Success on success, otherwise an error.
32
	 */
33
	public static function checkAPI($keyID, $vCode)
34
	{
35
		$keyID = trim($keyID);
36
		$vCode = trim($vCode);
37
		if ($keyID == "" || $vCode == "")
38
			return "Error, no keyID and/or vCode";
39
		$keyID = (int)$keyID;
40
		if ($keyID == 0) {
41
			return "Invalid keyID.  Did you get the keyID and vCode mixed up?";
42
		}
43
44
		$pheal = Util::getPheal($keyID, $vCode);
45
		try
46
		{
47
			$result = $pheal->accountScope->APIKeyInfo();
48
		}
49
		catch (Exception $e)
50
		{
51
			if (strlen($keyID) > 20)
52
				return "Error, you might have mistaken keyid for the vcode";
53
			return "Error: " . $e->getCode() . " Message: " . $e->getMessage();
54
		}
55
56
		$key = $result->key;
57
		$accessMask = $key->accessMask;
58
		$hasBits = self::hasBits($accessMask);
59
60
		if (!$hasBits) {
61
			return "Error, key does not have access to killlog, please modify key to add killlog access";
62
		}
63
		return "success";
64
	}
65
66
	/**
67
	 * Adds a key to the database.
68
	 *
69
	 * @static
70
	 * @param int $keyID
71
	 * @param string $vCode
72
	 * @param null|string $label
73
	 * @return string
74
	 */
75
	public static function addKey($keyID, $vCode, $label = null)
76
	{
77
		$userID = User::getUserID();
78
		if ($userID == null) $userID = 0;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $userID of type integer|null against null; this is ambiguous if the integer can be zero. Consider using a strict comparison === instead.
Loading history...
79
80
		$exists = Db::queryRow("SELECT userID, keyID, vCode FROM zz_api WHERE keyID = :keyID AND vCode = :vCode", array(":keyID" => $keyID, ":vCode" => $vCode), 0);
81
		if ($exists == null) {
82
			// Insert the api key
83
			Db::execute("replace into zz_api (userID, keyID, vCode, label) VALUES (:userID, :keyID, :vCode, :label)", array(":userID" => $userID, ":keyID" => $keyID, ":vCode" => $vCode, ":label" => $label));
84
		} else if ($exists["userID"] == 0) {
85
			// Someone already gave us this key anonymously, give it to this user
86
			Db::execute("UPDATE zz_api SET userID = :userID, label = :label WHERE keyID = :keyID", array(":userID" => $userID, ":label" => $label, ":keyID" => $keyID));
87
			return "keyID $keyID previously existed in our database but has now been assigned to you.";
88
		} else {
89
			return "keyID $keyID is already in the database...";
90
		}
91
92
		$pheal = Util::getPheal($keyID, $vCode);
93
		$result = $pheal->accountScope->APIKeyInfo();
94
		$key = $result->key;
95
		$keyType = $key->type;
96
97
		if ($keyType == "Account") $keyType = "Character";
98
99
		$ip = IP::get();
100
101
		Log::log("API: $keyID has been added.  Type: $keyType ($ip)");
102
		return "Success, your $keyType key has been added.";
103
	}
104
105
	/**
106
	 * Deletes a key owned by the currently logged in user.
107
	 *
108
	 * @static
109
	 * @param $keyID int
110
	 * @return string
111
	 */
112
	public static function deleteKey($keyID)
113
	{
114
		$userID = user::getUserID();
115
		Db::execute("DELETE FROM zz_api_characters WHERE keyID = :keyID", array(":keyID" => $keyID));
116
		Db::execute("DELETE FROM zz_api WHERE userID = :userID AND keyID = :keyID", array(":userID" => $userID, ":keyID" => $keyID));
117
		return "$keyID has been deleted";
118
	}
119
120
	/**
121
	 * Returns a list of keys owned by the currently logged in user.
122
	 *
123
	 * @static
124
	 * @param $userID int
125
	 * @return array Returns
126
	 */
127
	public static function getKeys($userID)
128
	{
129
		if(!isset($userID))
130
			$userID = user::getUserID();
131
132
		$result = Db::query("SELECT keyID, vCode, label, lastValidation, errorCode FROM zz_api WHERE userID = :userID order by keyID", array(":userID" => $userID), 0);
133
		return $result;
134
	}
135
136
	/**
137
	 * Returns an array of character keys.
138
	 *
139
	 * @static
140
	 * @param $userID int
141
	 * @return array Returns
142
	 */
143
	public static function getCharacterKeys($userID)
144
	{
145
		$result = Db::query("select c.* from zz_api_characters c left join zz_api a on (c.keyID = a.keyID) where a.userID = :userID", array(":userID" => $userID), 0);
146
		return $result;
147
	}
148
149
	/**
150
	 * Returns an array of the characters assigned to this user.
151
	 *
152
	 * @static
153
	 * @param $userID int
154
	 * @return array
155
	 */
156
	public static function getCharacters($userID)
157
	{
158
		$db = Db::query("SELECT characterID FROM zz_api_characters c left join zz_api a on (c.keyID = a.keyID) where userID = :userID", array(":userID" => $userID), 0);
159
		$results = Info::addInfo($db);
160
		return $results;
161
	}
162
163
	/**
164
	 * Tests the access mask for KillLog access
165
	 *
166
	 * @static
167
	 * @param int $accessMask
168
	 * @return bool
169
	 */
170
	public static function hasBits($accessMask)
171
	{
172
		return ((int)($accessMask & 256) > 0);
173
	}
174
175
	/**
176
	 * API exception handling
177
	 *
178
	 * @static
179
	 * @param integer $keyID
180
	 * @param int $charID
181
	 * @param Exception $exception
182
	 * @return void
183
	 */
184
	public static function handleApiException($keyID, $charID, $exception)
185
	{
186
		$code = $exception->getCode();
187
		$message = $exception->getMessage();
188
		$clearCharacter = false;
189
		$clearAllCharacters = false;
190
		$clearApiEntry = false;
191
		$updateCacheTime = false;
192
		$demoteCharacter = false;
193
		$cacheUntil = 0;
194
		switch ($code) {
195
			case 28: // Timeouts
196
			case 904: // temp ban from ccp's api server
197
				Db::execute("replace into zz_storage values ('ApiStop904', date_add(now(), interval 5 minute))");
198
			break;
199
200
			case 403:
201
			case 502:
202
			case 503: // Service Unavailable - try again later
203
				$cacheUntil = time() + 300;
204
				$updateCacheTime = true;
205
			break;
206
207
			case 119: // Kills exhausted: retry after [{0}]
208
				$cacheUntil = $exception->cached_until;
0 ignored issues
show
The property cached_until does not seem to exist in Exception.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
209
				$updateCacheTime = true;
210
			break;
211
212
			case 120: // Expected beforeKillID [{0}] but supplied [{1}]: kills previously loaded.
213
				$cacheUntil = $exception->cached_until;
214
				$updateCacheTime = true;
215
			break;
216
217
			case 221: // Demote toon, illegal page access
218
				$clearAllCharacters = true;
219
				$clearApiEntry = true;
220
			break;
221
222
			case 220:
223
			case 200: // Current security level not high enough.
224
				// Typically happens when a key isn't a full API Key
225
				$clearAllCharacters = true;
226
				$clearApiEntry = true;
227
				//$code = 203; // Force it to go away, no point in keeping this key
228
			break;
229
230
			case 522:
231
			case 201: // Character does not belong to account.
232
				// Typically caused by a character transfer
233
				$clearCharacter = true;
234
			break;
235
			case 207: // Not available for NPC corporations.
236
			case 209:
237
				$demoteCharacter = true;
238
			break;
239
240
			case 222: // account has expired
241
				$clearAllCharacters = true;
242
				$clearApiEntry = true;
243
				$cacheUntil = time() + (7 * 24 * 3600); // Try again in a week
244
			break;
245
246
			case 403:
247
			case 211: // Login denied by account status
248
				// Remove characters, will revalidate with next doPopulate
249
				$clearAllCharacters = true;
250
				$clearApiEntry = true;
251
			break;
252
253
			case 202: // API key authentication failure.
254
			case 203: // Authentication failure - API is no good and will never be good again
255
			case 204: // Authentication failure.
256
			case 205: // Authentication failure (final pass).
257
			case 210: // Authentication failure.
258
			case 521: // Invalid username and/or password passed to UserData.LoginWebUser().
259
				$clearAllCharacters = true;
260
				$clearApiEntry = true;
261
			break;
262
263
			case 500: // Internal Server Error (More CCP Issues)
264
			case 520: // Unexpected failure accessing database. (More CCP issues)
265
			case 404: // URL Not Found (CCP having issues...)
266
			case 902: // Eve backend database temporarily disabled
267
				$updateCacheTime = true;
268
				$cacheUntil = time() + 3600; // Try again in an hour...
269
			break;
270
271
			case 0: // API Date could not be read / parsed, original exception (Something is wrong with the XML and it couldn't be parsed)
272
			default: // try again in 5 minutes
273
				Log::log("$keyID - Unhandled error - Code $code - $message");
274
				//$updateCacheTime = true;
275
				$clearApiEntry = true;
276
				//$cacheUntil = time() + 300;
277
			break;
278
		}
279
280
		if ($demoteCharacter && $charID != 0) {
281
			if (false === Db::execute("update zz_api_characters set isDirector = 'F' where characterID = :charID", array(":charID" => $charID), false)) {
282
				$clearCharacter = true;
283
			}
284
		}
285
286
		if ($clearCharacter && $charID != 0) {
287
			Db::execute("delete from zz_api_characters where keyID = :keyID and characterID = :charID", array(":keyID" => $keyID, ":charID" => $charID));
288
		}
289
290
		if ($clearAllCharacters) {
291
			Db::execute("delete from zz_api_characters where keyID = :keyID", array(":keyID" => $keyID));
292
		}
293
294
		if ($clearApiEntry) {
295
			Db::execute("update zz_api set errorCode = :code where keyID = :keyID", array(":keyID" => $keyID, ":code" => $code));
296
		}
297
298
		if ($updateCacheTime && $cacheUntil != 0 && $charID != 0) {
299
			Db::execute("update zz_api_characters set cachedUntil = :cacheUntil where characterID = :charID",
300
					array(":cacheUntil" => $cacheUntil, ":charID" => $charID));
301
		}
302
		Db::execute("update zz_api_characters set errorCode = :code where keyID = :keyID and characterID = :charID", array(":keyID" => $keyID, ":charID" => $charID, ":code" => $code));
303
	}
304
305
	public static function fetchApis()
306
	{
307
		global $baseDir;
308
309
		Db::execute("delete from zz_api_characters where isDirector = ''"); // Minor cleanup
310
		$fetchesPerSecond = (int) Storage::retrieve("APIFetchesPerSecond", 30);
311
		$maxModulus = Db::queryField("select max(modulus) maxModulus from zz_api_characters", "maxModulus", array(), 0);
312
		// If the fetchesPerSecond has changed we need to update the modulus on all rows to make sure everyone gets a turn
313
		if (($maxModulus + 1) != $fetchesPerSecond)
314
		{
315
			Log::log("Updating modulus in zz_api_characters table...");
316
			Db::execute("update zz_api_characters set modulus = null");
317
		}
318
		Db::execute("update zz_api_characters set modulus = (apiRowID % :modulus) where modulus is null", array(":modulus" => $fetchesPerSecond));
319
320
		for ($i = 0; $i < $fetchesPerSecond; $i++)
321
		{
322
			$command = "flock -w 60 $baseDir/cache/locks/preFetch.$i php5 $baseDir/cli.php apiFetchKillLog $i $fetchesPerSecond";
323
			$command = escapeshellcmd($command);
324
			exec("$command >/dev/null 2>/dev/null &");
325
		}
326
	}
327
328
	public static function doApiSummary()
329
	{
330
		$lastActualKills = Db::queryField("select contents count from zz_storage where locker = 'actualKills'", "count", array(), 0);
331
		$actualKills = Db::queryField("select count(*) count from zz_killmails where processed = 1", "count", array(), 0);
332
333
		$lastTotalKills = Db::queryField("select contents count from zz_storage where locker = 'totalKills'", "count", array(), 0);
334
		$totalKills = Db::queryField("select count(*) count from zz_killmails", "count", array(), 0);
335
336
		Db::execute("replace into zz_storage (locker, contents) values ('totalKills', $totalKills)");
337
		Db::execute("replace into zz_storage (locker, contents) values ('actualKills', $actualKills)");
338
		Db::execute("delete from zz_storage where locker like '%KillsProcessed'");
339
340
		$actualDifference = number_format($actualKills - $lastActualKills, 0);
341
		$totalDifference = number_format($totalKills - $lastTotalKills, 0);
342
343
		Log::irc("|g|$actualDifference|n| mails processed | |g|$totalDifference|n| kills added");
344
	}
345
346
	/**
347
	 * @param string $keyID string
348
	 * @param $charID int
349
	 * @param $killlog string
350
	 * @return int
351
	 */
352
	public static function processRawApi($keyID, $charID, $killlog)
0 ignored issues
show
The parameter $charID is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
353
	{
354
		$count = 0;
355
		foreach ($killlog->kills as $kill) {
356
			$killID = $kill->killID;
357
358
			$json = json_encode($kill->toArray());
359
			$hash = Util::getKillHash(null, $kill);
360
361
			$inDb = Db::queryField("select count(1) count from zz_killmails where killID = :killID", "count", array(":killID" => $killID), 0);
362
			if ($inDb == 0)
363
			{
364
				$added = Db::execute("insert ignore into zz_killmails (killID, hash, source, kill_json) values (:killID, :hash, :source, :json)",
365
						array(":killID" => $killID, ":hash" => $hash, ":source" => "keyID:$keyID", ":json" => $json));
366
				$count += $added;
367
			}
368
		}
369
		return $count;
370
	}
371
}
372