|
| 1 | ++++ |
| 2 | +date = '2025-11-03T12:00:00+08:00' |
| 3 | +draft = false |
| 4 | +title = '[2025]N1CTF WP for n1cat,eezzjs' |
| 5 | +author='GSBP' |
| 6 | +categories=["Java安全","CVE","N1CTF","WP"] |
| 7 | + |
| 8 | ++++ |
| 9 | + |
| 10 | +## TL;DR |
| 11 | + |
| 12 | +It's my first time to create challenges after i entered Nu1L Team. I'm glad to see so many hackers could solve my challenges though they have few problems(Such as in eezzjs, flag is in `/flag` instead of `/ffffflag` because my new attachment did not update on competition platform in time). Here are my expected solutions for these challenges |
| 13 | + |
| 14 | +## eezzjs |
| 15 | + |
| 16 | +In this challenges, your first work is to get a legal JWT that you could pass `authenticateJWT` middleware and use `/upload` to upload your file arbitrarily. |
| 17 | + |
| 18 | +Actually it is easy to find this vuln. Beacuse when you try to debug locally, you could find information in this image |
| 19 | + |
| 20 | + |
| 21 | + |
| 22 | +When you run `npm audit`, you got the vuln is in sha.js,even it tells you the advisory of this vuln XD. |
| 23 | + |
| 24 | + |
| 25 | + |
| 26 | +The Principle of sha.js vuln is when you submit an Object as `update()` 's arg, you could find `length` is assign by `data.length`,so this._len can be control that if data is an Object and it has a member named `length` |
| 27 | + |
| 28 | +``` |
| 29 | +Hash.prototype.update = function (data, enc) { |
| 30 | + if (typeof data === 'string') { |
| 31 | + enc = enc || 'utf8' |
| 32 | + data = Buffer.from(data, enc) |
| 33 | + } |
| 34 | +
|
| 35 | + var block = this._block |
| 36 | + var blockSize = this._blockSize |
| 37 | + var length = data.length |
| 38 | + var accum = this._len |
| 39 | +
|
| 40 | + for (var offset = 0; offset < length;) { |
| 41 | + var assigned = accum % blockSize |
| 42 | + var remainder = Math.min(length - offset, blockSize - assigned) |
| 43 | +
|
| 44 | + for (var i = 0; i < remainder; i++) { |
| 45 | + block[assigned + i] = data[offset + i] |
| 46 | + } |
| 47 | +
|
| 48 | + accum += remainder |
| 49 | + offset += remainder |
| 50 | +
|
| 51 | + if ((accum % blockSize) === 0) { |
| 52 | + this._update(block) |
| 53 | + } |
| 54 | + } |
| 55 | +
|
| 56 | + this._len += length |
| 57 | + return this |
| 58 | +} |
| 59 | +``` |
| 60 | + |
| 61 | +where `this._len` equals to zero,its value always becomes the same.Then you could pass `authenticateJWT` and upload your file. |
| 62 | + |
| 63 | +Next step,you should use `/upload` and `/` try to get RCE |
| 64 | + |
| 65 | +As we all know,when ejs try to template a view in web.it would try to run this code |
| 66 | + |
| 67 | +``` |
| 68 | + ... |
| 69 | + if (!opts.engines[this.ext]) { |
| 70 | + // load engine |
| 71 | + var mod = this.ext.slice(1) |
| 72 | + debug('require "%s"', mod) |
| 73 | +
|
| 74 | + // default engine export |
| 75 | + var fn = require(mod).__express |
| 76 | +
|
| 77 | + if (typeof fn !== 'function') { |
| 78 | + throw new Error('Module "' + mod + '" does not provide a view engine.') |
| 79 | + } |
| 80 | + ... |
| 81 | +``` |
| 82 | + |
| 83 | +when you submit `?templ=abc.ddw`,it would try to require ddw modules. It gives us a chance to run arbitrary code. |
| 84 | + |
| 85 | + |
| 86 | + |
| 87 | +But we couldn't create dir or `js` file.How do we attack? |
| 88 | + |
| 89 | +In [documents]("https://nodejs.org/api/modules.html") we could know |
| 90 | + |
| 91 | +> If the exact filename is not found, then Node.js will attempt to load the required filename with the added extensions: `.js`, `.json`, and finally `.node`. When loading a file that has a different extension (e.g. `.cjs`), its full name must be passed to `require()`, including its file extension (e.g. `require('./file.cjs')`). |
| 92 | +
|
| 93 | +So we could use `.node` file to finish our attack,[My exploit]("https://github.com/Nu1LCTF/n1ctf-2025/tree/main/web/eezzjs/solution") |
| 94 | + |
| 95 | +At last, i felt sorry for this challenge really has some issues,and there many unexpected solutions can solve this challenge that could use simply `../` or `./` bypass my ez waf haha. |
| 96 | + |
| 97 | +## n1cat |
| 98 | + |
| 99 | +n1cat is a gray challenge because if i provide full source code, the first step( `CVE-2025-55752`) is completely useless XD. In other words you could use this vulnerability to get full source code(maybe some libs was confused). |
| 100 | + |
| 101 | +My attachment provide a Tomcat file `rewrite.config`,then you know `CVE-2025-55752` is a point of this challenge. You could try to read `web.xml`. |
| 102 | + |
| 103 | +``` |
| 104 | +<?xml version="1.0" encoding="UTF-8"?> |
| 105 | +<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" |
| 106 | + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
| 107 | + xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" |
| 108 | + version="4.0"> |
| 109 | + <servlet> |
| 110 | + <servlet-name>welcomeServlet</servlet-name> |
| 111 | + <servlet-class>ctf.n1cat.welcomeServlet</servlet-class> |
| 112 | + </servlet> |
| 113 | +
|
| 114 | + <servlet-mapping> |
| 115 | + <servlet-name>welcomeServlet</servlet-name> |
| 116 | + <url-pattern>/</url-pattern> |
| 117 | + </servlet-mapping> |
| 118 | +</web-app> |
| 119 | +
|
| 120 | +``` |
| 121 | + |
| 122 | +then you know `welcomeServlet` class path,you could use the same way to down it. The same applies to the`User` class and detect lib. |
| 123 | + |
| 124 | +User.class |
| 125 | + |
| 126 | +``` |
| 127 | +package ctf.n1cat; |
| 128 | +
|
| 129 | +import javax.naming.InitialContext; |
| 130 | +import javax.naming.NamingException; |
| 131 | +
|
| 132 | +public class User { |
| 133 | + private String name; |
| 134 | + private String word; |
| 135 | + private String url; |
| 136 | +
|
| 137 | + public User(){ |
| 138 | + } |
| 139 | + public String getName() { |
| 140 | + return name; |
| 141 | + } |
| 142 | + public String getWord() { |
| 143 | + return word; |
| 144 | + } |
| 145 | + public void setWord(String password) { |
| 146 | + this.word = password; |
| 147 | + } |
| 148 | +
|
| 149 | + public void setName(String name) throws NamingException { |
| 150 | + this.name = name; |
| 151 | + } |
| 152 | + public String getUrl() { |
| 153 | + return url; |
| 154 | + } |
| 155 | + public void setUrl(String url) { |
| 156 | + try{ |
| 157 | + new InitialContext().lookup(url); |
| 158 | + } catch (NamingException e) { |
| 159 | + throw new RuntimeException(e); |
| 160 | + } |
| 161 | + } |
| 162 | +} |
| 163 | +
|
| 164 | +``` |
| 165 | + |
| 166 | +You could directly find a JNDI Injection vuln. Now first step is over. |
| 167 | + |
| 168 | +The second step is try to use this vulnerability to get an rce.JDK version is 17,many ways of JNDI attack might not working.I uses RMI communicate deserialize(Communication between the RMI server and RMI client employs serialisation and deserialisation).About deserialize chains,we uses Jackson+SpringAOP to solve this (You could find `Jackson` dependence in `welcomeServlet`,`SpringAOP`dependence and version could use `CVE-2025-55752` to detect). |
| 169 | + |
| 170 | +About this chains analysis,could see [this]("https://fushuling.com/index.php/2025/08/21/%e9%ab%98%e7%89%88%e6%9c%acjdk%e4%b8%8b%e7%9a%84spring%e5%8e%9f%e7%94%9f%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e9%93%be/") |
| 171 | + |
| 172 | +[My exploit]("https://github.com/Nu1LCTF/n1ctf-2025/tree/main/web/n1cat/solution") |
| 173 | + |
| 174 | + |
| 175 | + |
| 176 | +## END |
| 177 | + |
| 178 | +All challenges and solutions has uploaded on [GitHub]("https://github.com/Nu1LCTF/n1ctf-2025").Hope next time will be better |
0 commit comments