1401 - Bypass

2026. 3. 20. 17:31보안/DreamHACK

코드를 분석해보자!

 

 

이번 문제는 실행환경과 소스코드를 함께 받아온 케이스다.

  • docker-compose.yml : 컨테이너를 어떻게 띄울지 포트, 폴더, 서비스 이름 정의
  • deploy/Dockerfile : 컨테이너 이미지를 어떻게 만들지 설치, 실행 명령 정의
  • deploy/app.js : 메인 서버 코드

 

docker-compose.yml

version: "3.9"
services:
  nginx:
    image: nginx:1.22.0
    ports:
      - "8000:80"
    volumes:
      - ./deploy/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - app
  app:
    build:
      context: ./deploy/app

 

8000 접속 시 먼저 nginx를 거친 후 app에 들어간다는 것을 알 수 있다.

 

 

nginx.conf

events {
    worker_connections  1024;
}

http {
    server {
        listen 80;
        listen [::]:80;
        server_name  _;
        
        location = /shop {
            deny all;
        }

        location = /shop/ {
            deny all;
        }

        location / {
            proxy_pass http://app:3000/;
        }

    }

}

 

/shop 요청과 /shop/ 요청을 차단한다.

하지만 완전히 동일한 문자열만 막으므로 대소문자나 슬래시 변형, 문자 추가로 통과할 수 있다.

 

 

app.js

app.post("/shop",(req, res)=>{
    const leg = req.body.leg

    if (leg == 'FLAG'){
        return res.status(403).send("Access Denied")
    }

 

leg는 사용자 입력값에서 꺼낸다는 것을 알 수 있다.

 

app은 정확히 "FLAG"를 403으로 접근을 차단한다.

 

 

const obj = search(words,leg)

--------------------------------------------------------------

function search(words, leg) {
	return words.find(word => word.name === leg.toUpperCase())
}

 

사용자가 입력한 값을 대문자로 변환한 후, words 배열에서 name이 일치하는 데이터를 찾아 반환한다.

 

 

const words = require("./ag")

 

words 배열은 ag.js에서 가져온다!

 

 

ag.js

module.exports = [
    {
        "id": 1,
        "name": "FLAG",
        "description": "DH{fake_flag}"
    }
    
    ...

 

name 값을 기준으로 대문자로 변환된 입력값과 비교하여 FLAG 데이터를 찾을 수 있다.

 

 

 

⭐ 취약점

if (leg == 'FLAG'){
  return res.status(403).send("Access Denied")

 

사용자가 입력한 값이 정확히 "FLAG"인 경우에만 차단한다.

 

function search(words, leg) {
	return words.find(word => word.name === leg.toUpperCase())
}

 

search 함수가 실행되어 어떤 대소문자의 조합의 flag도 다 FLAG로 바뀐다.

 

필터는 원본 기준이나 검색은 대문자로 변환되는 것이 문제다.

필터는 FLAG만 막지만 입력은 대문자로 바꿔서 사용하므로 사용 기준이 다르다.

 

 

 


f12를 눌러 콘솔 탭에 들어간다.

fetch('/shop', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: 'leg=flag'
})
.then(r => r.text())
.then(console.log)

 

브라우저에서 /shop으로 요청을 보내면 403 응답으로 접근을 차단한다.

응답에서 알 수 있듯이 이는 nginx에서 막은 것이다.

 

브라우저는 이미 정상 경로(/shop)으로 요청을 보내기 때문에

요청을 내가 직접 바꿔서 보낼 수 있어야 한다.

원하는 요청을 직접 만들 수 있는 curl 을 활용 할 수 있다!

 

curl -X POST "http://host3.dreamhack.games:19225/SHOP" -d "leg=flag"

 

/shop 대신 /SHOP 으로 입력 시

nginx는 다른 경로라고 생각하고 이를 통과시킨다.

Express는 경로 대소문자를 엄격히 구분하지 않아서 /SHOP 요청을 /shop 요청으로 받는다.

또한 정확히 "FLAG"만 막기에 "flag"는 통과하는 취약점이 발생한다.

 

 

 

문제 해결 성공! 😁

'보안 > DreamHACK' 카테고리의 다른 글

768 - Command Injection  (0) 2026.03.20
769 - SQL Injection  (0) 2026.03.20