username:password@
목차
1. Vuln
- JS Prototype 기반 인증 우회
- SSRF
2. Code
전체 코드 접기/펼치기
// 모듈 불러오기
const express = require('express');
const basicAuth = require('express-basic-auth');
const fetch = require('node-fetch');
const fs = require('fs');
// 앱 생성, 포트 설정, 플래그 읽기
const app = express();
const port = 3000;
const flag = fs.readFileSync('flag.txt', 'utf8');
// 랜덤 hex 문자열 생성 함수
const genRanHex = size =>
[...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
// 사용자 저장 객체: users
const users = {
// admin: 랜덤 hex 16자리 문자열
'admin': genRanHex(16),
};
// basic auth 검사 수행
const loginRequired = basicAuth({
// 사용자가 입력한 username, password를 받아 users 객체에 저장된 값과 비교
authorizer: (username, password) => users[username] == password,
unauthorizedResponse: 'Unauthorized',
});
// 관리자 전용
const adminOnly = (req, res, next) => {
// 로그인한 사용자 중 admin만 통과
if (req.auth?.user == 'admin') {
return next();
}
return res.status(403).send('Only admin can access this resource');
};
// GET /
app.get('/', (req, res) => {
// http://username:password@host
res.send('login with http://username:password@...');
});
// GET /register
app.get('/register', (req, res) => {
// 기능 없음
res.send('Not implemented');
});
// GET /report
// 로그인 필요
app.get('/report', loginRequired, (req, res) => {
// 쿼리 파라미터 path 읽기
const path = req.query.path;
// path 없을 때 html 폼 반환
if (!path) {
return res.send("<form method='GET'>http://<input name='path' /><button>Submit</button></form>");
}
// 서버가 직접 외부에 http 요청을 보냄
// url 형태: http://admin:<random_password>@${path}
// 서버가 자기 내부에만 admin 자격 증명을 붙여서 path로 접속
fetch(`https://admin:${users["admin"]}@${path}`)
.then(() => res.send("Success"))
.catch(() => res.send("Error"));
});
// GET /admin
// loginRequired -> adminOnly 실행
app.get('/admin', loginRequired, adminOnly, (req, res) => {
// flag 반환
res.send(flag);
});
// 앱 실행
app.listen(port, '0.0.0.0', () => {
console.log(`Server listening at http://localhost:${port}`);
});
loginRequired 함수에서 취약점이 존재한다.
authorizer: (username, password) => users[username] == password
===가 아니라 ==를 쓰고 있고, users가 일반 객체이기 때문에 상속 프로퍼티 접근이 가능하다.
우회 후보:
toString- username:
toString - password:
function toString() { [native code] }
- username:
constructor- username:
constructor - password:
function Object() { [native code] }
- username:
__proto__- username:
__proto__ - password:
[object Object]
- username:
3. Payload
3.1. /report 인증 우회

toString:function toString() { [native code] }를 base64로 인코딩해서 Authorization에 붙인다.

결과
3.2. /report에서 관리자 비밀번호 유출

path를 내 requestbin 주소를 향하게 하고, 같은 방식으로 인증을 한다.

requestbin에서 요청 결과를 확인할 수 있다.

base64로 디코딩하면 admin의 랜덤 비밀번호를 확인할 수 있다.
3.3. 유출한 admin 계정으로 /admin 접근

방금 확인한 admin 비밀번호를 이용해 base64 인코딩 후 요청을 보낸다.

flag를 확인할 수 있다.
Comments