IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> PHP知识库 -> PHP代码审计(1) -> 正文阅读

[PHP知识库]PHP代码审计(1)

extract变量覆盖

了解PHP extract函数点我
贴段代码:

<?php

$flag='xxx'; 
extract($_GET);
 if(isset($shiyan))
 { 
    $content=trim(file_get_contents($flag));
    if($shiyan==$content)
    { 
        echo'ctf{xxx}'; 
    }
   else
   { 
    echo'Oh.no';
   } 
   }

?>

trim()函数为去首位空字符
file_get_contents()函数将一个文件读入一个字符串中

  • 有一个变量flag=‘xxx’; extract通过GET传入一个数组,将键名变为变量名,键值变为变量值
  • isset($shiyan)判断是否有这个变量,如果有,就将flag变量里面的值去首尾空后赋值给$content
  • 之后再判断$shiyan$content是否相等,若相等,输出flag
  • 这里如果flag传入的GET传入的值如果不为文件名,$content就一直为空,我们可以使得shiyan为空,则flag就可以为任意值
    构造payload:
?shiyan=&flag=1  
//两个变量作为一个数组被GET接收


绕过过滤的空白字符

贴代码:

<?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()
 
}
 
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 — 使用反斜线引用字符串
    } 
} 
 
 
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; 
} 
 
 
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!! ";  
 
}
else
{
 
     $value1 = intval($req["number"]);
     $value2 = intval(strrev($req["number"]));  //strrev --将字符反转
 
     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;

通过代码审计之后:(需要满足这三个条件)

  • GET提交的值不能是数字包括小数

is_numeric()函数和req[‘number’]!=strval(intval(req[‘number’]))

  • 提交的number值要求是一个回文数

v a l u e 1 = i n t v a l ( value1 = intval( value1=intval(req[“number”]);
v a l u e 2 = i n t v a l ( s t r r e v ( value2 = intval(strrev( value2=intval(strrev(req[“number”])); //strrev --将字符反转
if( v a l u e 1 ! = value1!= value1!=value2){
$info=“no, this is not a palindrome number!”;
}

  • number不能是一个回文数

if(is_palindrome_number($req[“number”]))

法一

可以引入\f(%0c)在数字前面,来绕过is_palindrome_number函数,至于前面数字的判断,因为is_numeric()函数intval都会忽略这个字符,所以不会影响
而第一个条件的解决方案有两种:
1.可以通过%00数字前后都可以和%20只能在数字后面
2.再POST一个number参数将GET中的给覆盖掉
则可以构造payload:

?number=%00%0C191
法二

Fuzzing思路
?number=%00%2B191%2B解析后为“+”; +191==191;intval(‘191+’)==191
可以简化代码为:

<?php
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;
}
$a = trim($_GET['number']);
var_dump(($a==strval(intval($a)))&(intval($a)==intval(strrev($a)))&!is_palindrome_number($a))
?>

进行Fuzzing

import requests
for i in range(256) :
	respond = requests.get("http://127.0.0.1/2.php/index.php?number=%s191"%("%%%02X"%i))
	if '1' in respond.text :
		print("%%%02X"%i)

结果如下:

%0C  
%2B


多重加密

老规矩 --贴代码

<?php
    include 'common.php';
    $requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
    //把一个或多个数组合并为一个数组
    class db
    {
        public $where;
        function __wakeup()
        {
            if(!empty($this->where))
            {
                $this->select($this->where);
            }
        }
        function select($where)
        {
            $sql = mysql_query('select * from user where '.$where);
            //函数执行一条 MySQL 查询。
            return @mysql_fetch_array($sql);
            //从结果集中取得一行作为关联数组,或数字数组,或二者兼有返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false
        }
    }

    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');
    }

?> 

通过代码审计可以看到

        if($login['user'] === 'ichunqiu')
        {
            echo $flag;
        $login = unserialize(gzuncompress(base64_decode($requset['token'])));
        //gzuncompress:进行字符串压缩
        //unserialize: 将已序列化的字符串还原回 PHP 的值
$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);

这就是加密的过程,我们可以反向解密
构造php代码,生成token得到flag

$a = array('user'=>'ichunqiu');
$b = base64_encode(gzcompress(serialize($a)));
print_r($b);

得到token

eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==


SQL注入-with rollup绕过

贴代码:

<?php
error_reporting(0);

if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
    echo '<form action="" method="post">'."<br/>";
    echo '<input name="uname" type="text"/>'."<br/>";
    echo '<input name="pwd" type="text"/>'."<br/>";
    echo '<input type="submit" />'."<br/>";
    echo '</form>'."<br/>";
    echo '<!--source: source.txt-->'."<br/>";
    die;
}

function AttackFilter($StrKey,$StrValue,$ArrReq){  
    if (is_array($StrValue)){

//检测变量是否是数组

        $StrValue=implode($StrValue);

//返回由数组元素组合成的字符串

    }
    if (preg_match("/".$ArrReq."/is",$StrValue)==1){   

//匹配成功一次后就会停止匹配

        print "水可载舟,亦可赛艇!";
        exit();
    }
}

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){ 

//遍历数组

    AttackFilter($key,$value,$filter);
}

$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
    die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);

//设置活动的 MySQL 数据库

$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql); 

//执行一条 MySQL 查询

if (mysql_num_rows($query) == 1) { 

//返回结果集中行的数目

    $key = mysql_fetch_array($query);

//返回根据从结果集取得的行生成的数组,如果没有更多行则返回 false

    if($key['pwd'] == $_POST['pwd']) {
        print "CTF{XXXXXX}";
    }else{
        print "亦可赛艇!";
    }
}else{
    print "一颗赛艇!";
}
mysql_close($con);
?> 

根据代码审计之后可以知道有三个绕过

$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
  • 不能使用上述关键字
  • SQL数据库的影响为1
if (mysql_num_rows($query) == 1)
  • 绕过最后一个if语句
if($key['pwd'] == $_POST['pwd'])

第一个条件,我们可以直接不适用关键词即可
第二个条件,返回结果只能有一条,我们来测试用户人数,但是上面的关键词,又被过滤了,我们可以考虑使用limit y offset x来判断人数
PS:
SQL查询语句中limit和offset的区别
limit y分句表示:读取y条数据
limit x,y分句表示:跳过x条数据,读取y条数据
limit y offset x分句表示:跳过x条数据,读取y条数据

这里我们可以一个一个的尝试:
1’ or 1=1 limit 1 offset 0# - - - 返回亦可赛艇 - - -用户数为1
1’ or 1=1 limit 1 offset 1# - - - 返回亦可赛艇 - - -用户数为2
1’ or 1=1 limit 1 offset 2# - - - 返回一颗赛艇 - - - 没有第三个用户
推出只有两个用户

第三个条件,输入的密码的值要和数据库中的密码一样
这里可以用语句 group by pwd with rollup(分组后会多一行进行统计)
由于对字符串统计无效的第三行的pwd结果是NULL这样就可以绕过最后一个if语句了
构造payload :

?uname=1' or 1=1 group by pwd with rollup limit 1 offset 2#

就不需要输入密码了,让密码为NULL



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>'; 
      }
   } 
?>

经过代码审计后发现需要三个条件:

  • ereg ("^[a-zA-Z0-9]+$", $_GET['password']) !==FALSE即代表传入的password只能是0-9的数字和大小写的a-z的字母
  • strlen($_GET['password']) < 8 && $_GET['password'] > 9999999即表示长度小于8且password的值要大于9999999
  • strpos ($_GET['password'], '*-*') !== FALSE即表示password中需要包含*-*这个字符

方法一:传入数组
ereg()函数和strpos()函数的参数不能够是数组,将会返回NULL,而且null!==FALSE,成功绕过
又因为数组的值比数字的值大,即条件二成功绕过
构造payload :

?password[]=1

方法二:%00截断
ereg()函数在遇到%00的时候就会停止
绕过条件二只需要利用科学计数法 例如 10的7次方=> 1e7
条件三那就更简单了,直接在后面加一个字符就好了
构造payload :

?password=1e7%00*-*

注意strpos函数大小写敏感



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()函数
这个函数在php5.3之前的版本中如果参数是不合法的值,函数将会报错而且会return 0导致虽然报错但是刚好绕过了strcmp()函数
当然后面修复了这个漏洞,只报错,不返回值


我们可以传入数组参数,strcmp函数会返回NULL,但是根据php弱类型,NULL==0
构造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>';
?>

===这个判断符号除了判断大小还会判断类型
比如sha()和md5()存在漏洞 sha1()默认传入的是字符串,我们传入数组他就会报错返回false
构造payload:

?name[]=1&password[]=2
  PHP知识库 最新文章
Laravel 下实现 Google 2fa 验证
UUCTF WP
DASCTF10月 web
XAMPP任意命令执行提升权限漏洞(CVE-2020-
[GYCTF2020]Easyphp
iwebsec靶场 代码执行关卡通关笔记
多个线程同步执行,多个线程依次执行,多个
php 没事记录下常用方法 (TP5.1)
php之jwt
2021-09-18
上一篇文章      下一篇文章      查看所有文章
加:2021-08-07 11:46:44  更:2021-08-07 11:47:31 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/28 17:38:58-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码