Post

(46) Dreamhack development-env 문제 풀이

문제 설명

초보 개발자 민수는 실수로 development 환경을 사용해 배포해버리고 말았습니다..

문제 풀이

사이트에 들어온 모습입니다.

소스코드를 보면 guest/guestPW라는 계정이 있습니다. 일단 로그인해보면 admin이 아니라고 합니다.

그럼 제공되는 소스코드를 살펴보겠습니다.

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
84
85
86
87
88
89
const express = require("express");
const cryptolib = require("./libs/customcrypto");
var cookieParser = require("cookie-parser");
var parsetrace = require("parsetrace");

const isDevelopmentEnv = true;

const app = express();
const port = 3000;

const flag = "DH{FAKE_FLAG}";
app.set("view engine", "ejs");

app.use(express.urlencoded({ extended: true }));
app.use(cookieParser());

let database = {
  guest: "guestPW",
  admin: cryptolib.generateRandomString(15),
}; //don't try to guess admin password

app.get("/", async (req, res) => {
  try {
    let token = req.cookies.auth || "";
    const payloadData = await cryptolib.readJWT(token, "FAKE_KEY");
    if (payloadData) {
      userflag = payloadData["uid"] == "admin" ? flag : "You are not admin";
      res.render("main", { username: payloadData["uid"], flag: userflag });
    } else {
      res.render("login");
    }
  } catch (e) {
    if (isDevelopmentEnv) {
      res.json(JSON.parse(parsetrace(e, { sources: true }).json()));
    } else {
      res.json({ message: "error" });
    }
  }
});

app.post("/validate", async (req, res) => {
  try {
    let contentType = req.header("Content-Type").split(";")[0];
    if (
      ["multipart/form-data", "application/x-www-form-urlencoded"].indexOf(
        contentType
      ) === -1
    ) {
      throw new Error("content type not supported");
    } else {
      let bodyKeys = Object.keys(req.body);
      if (bodyKeys.indexOf("id") === -1 || bodyKeys.indexOf("pw") === -1) {
        throw new Error("missing required parameter");
      } else {
        if (
          typeof database[req.body["id"]] !== "undefined" &&
          database[req.body["id"]] === req.body["pw"]
        ) {
          if (
            req.get("User-Agent").indexOf("MSIE") > -1 ||
            req.get("User-Agent").indexOf("Trident") > -1
          )
            throw new Error("IE is not supported");
          jwt = await cryptolib.generateJWT(req.body["id"], "FAKE_KEY");
          res
            .cookie("auth", jwt, {
              maxAge: 30000,
            })
            .send(
              "<script>alert('success');document.location.href='/'</script>"
            );
        } else {
          res.json({ message: "error", detail: "invalid id or password" });
        }
      }
    }
  } catch (e) {
    if (isDevelopmentEnv) {
      res.status(500).json({
        message: "devError",
        detail: JSON.parse(parsetrace(e, { sources: true }).json()),
      });
    } else {
      res.json({ message: "error", detail: e });
    }
  }
});

app.listen(port);

JWT를 이용해서 유저를 구분하고 있습니다.

JWT

JWT는 세 파트로 나누어지며, 각 파트는 .으로 구분됩니다. Header, Payload, Sinature로 이루어지며 aaaaa.bbbbb.ccccc형태입니다. 이때 각 요소는 Base64url 인코딩됩니다.
Header에는 토큰의 타입과 해시 암호화 알고리즘이 담겨있습니다.
Payload json형태의 데이터가 들어갑니다.
마지막으로 Signature는 secret key를 포함하여 암호화되어 있습니다.

Signature파트에서 암호화에 사용된 secret key를 알아낼 수 있다면 JWT를 위조해서 admin으로 접속할 수 있습니다.

이때 우리는 개발버전이라는 점을 이용해서 이 키를 알아낼 겁니다.
parsetrace(e, { sources: true }).json()형태로 사용한다면 개발에 도움을 주기 위해 에러가 발생한 라인과, 그 주변 라인의 정보를 담아서 반환합니다. 따라서 적절한 위치에서 에러를 유발할 수 있으면 소스코드를 확인할 수 있습니다.
에러가 난 줄의 위 아래로 3줄의 정보를 알려주기 때문에 throw와 “FAKE_KEY”가 가까이 있는 에러를 이용해야 합니다.

찾아보면

1
2
3
4
5
6
          if (
            req.get("User-Agent").indexOf("MSIE") > -1 ||
            req.get("User-Agent").indexOf("Trident") > -1
          )
            throw new Error("IE is not supported");
          jwt = await cryptolib.generateJWT(req.body["id"], "FAKE_KEY");

위와 같은 부분이 있습니다.
req의 header의 User-Agent값 중에서 MSIETrident가 들어있으면 IE is not supported에러를 throw 하도록 되어 있습니다.
이 에러를 유발시켜봅시다.

validate페이지에 접속하는 패킷을 리피터로 보내고 User-AgentMSIE를 추가하여 send 했습니다.

위와 같이 소스코드의 일부가 반환되는것을 볼 수 있고 찾아보면 kitvP5j71fwycLz라는 key가 노출되고 있습니다.

이제 구한 키를 가지고 여기에서 JWT를 생성해봅시다.

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwidWlkIjoiYWRtaW4ifQ.wQt9Fxeq7YtS5exzZ8x2hytp2zbE79YR5elSu5E1DiA

이걸 쿠키로 해서 접속해봅시다.

이렇게 쿠키를 수정해주고, 새로고침을 해보면 flag가 출력됩니다.

This post is licensed under CC BY 4.0 by the author.