본문 바로가기

카테고리 없음

SSRF 이론 및 실습해보기 - portswigger

기존의 웹 서비스는 단일 서비스로 구현할 수 있었지만 최근의 웹 서비스는 지원하는

기능이 증가함에 따라 구성요소가 증가하였다. 따라서 관리 및 코드의 복잡도를

낮추기 위하여 마이크로서비스들로 웹 서비스를 구현하는 추세가 되었다.

이때 각 마이크로서비스는 주로 HTTP, GRPC 등을 사용해 API 통신을 한다.

 

이처럼 서비스 간 HTTP 통신이 이뤄질 때 요청 내에 이용자의 입력값이 포함될 수 있다.

이용자의 입력값으로 포함되면 개발자가 의도하지 않은 요청이 전송될 수 있다.

Server-side Request Forgery(SSRF)는 웹 서비스의 요청을 변조하는 취약점으로

브라우저가 변조된 요청을 보내는 CSRF와는 다르게 웹 서비스의 권한으로 변조된 요청을 보낼 수 있다.

최근의 대다수 서비스들은 마이크로서비스로 구조를 많이 바꾸고, 새롭게 개발하는 추세이기 때문에

SSRF 취약점의 파급력이 더욱 높아지고 있다.

 

 

Server-side Request Forgery(SSRF)

웹 서비스는 외부에서 접근할 수 없는 내부망의 기능을 사용할 때가 있다.

관리자 페이지가 이에 해당하는데 이러한 서비스는 관리자만 이용할 수 있어야 하기 때문에

외부에서 접근할 수 없는 내부망에 위치한다.

 

웹 서비스는 외부에서 직접 접근할 수 없는 내부망 서비스와 통신할 수 있다.

만약 공격자가 SSRF 취약점을 통해 웹 서비스의 권한으로 요청을 보낼 수 있다면

공격자는 외부에서 간접적으로 내부망 서비스를 이용할 수 있고 이는 큰 피해를 입힐 수 있다.

 

웹 서비스가 보내는 요청을 변조하기 위해서는 요청 내에 이용자의 입력값이 포함돼야 한다.

입력값이 포함되는 예시로는 웹 서비스가 이용자가 입력한 URL에 요청을 보내거나 요청을 보낼 URL에

이용자 번호와 같은 내용이 사용되는 경우 그리고 이용자가 입력한 값이 HTTP BODY에 포함된 경우로 나눌 수 있다.

 

 

ex)이용자가 입력한 URL에 요청을 보내는 경우

from flask import Flask, request
import requests

app = Flask(__name__)
@app.route("/image_downloader")

def image_downloader():
    # 이용자가 입력한 URL에 HTTP 요청을 보내고 응답을 반환하는 페이지 입니다.
    image_url = request.args.get("image_url", "") # URL 파라미터에서 image_url 값을 가져옵니다.
    response = requests.get(image_url) # requests 라이브러리를 사용해서 image_url URL에 HTTP GET 메소드 요청을 보내고 결과를 response에 저장합니다.
    return ( # 아래의 3가지 정보를 반환합니다.
        response.content, # HTTP 응답으로 온 데이터
        200, # HTTP 응답 코드
        {"Content-Type": response.headers.get("Content-Type", "")}, # HTTP 응답으로 온 헤더 중 Content-Type(응답 내용의 타입)
    )

@app.route("/request_info")
def request_info():
    # 접속한 브라우저(User-Agent)의 정보를 출력하는 페이지 입니다.
    return request.user_agent.string
app.run(host="127.0.0.1", port=8000)

이용자가 입력한 image_url을 requests.get 함수를 사용해 GET 메소드로 HTTP 요청을 보내고 응답을 반환한다.

다음과 같은 URL을 입력하면 드림핵 페이지에 요청을 보내고 응답을 반환한다.

http://127.0.0.1:8000/image_downloader?image_url=https://dreamhack.io/assets/dreamhack_logo.png

해당 웹 페이지에 접속한 브라우저의 정보를 반환하는데 브라우저를 통해 해당 엔드포인트에 접근하면 접속하는데 사용된 브라우저가 출력된다. 위와 같이 접속하면 접속하는 사용한 브라우저가 출력된다.

http://127.0.0.1:8000/image_downloader?image_url=http://127.0.0.1:8000/request_info

하지만 지금과 같이 요청을 보낼 경우 브라우저 정보가 다르게 나온다.

문제점을 확인해보자. 해당 경로에 접속하면 image_downloader에서는 http://127.0.0.1:8000/request_info URL에 HTTP 요청을 보내고 응답을 반환한다. 반환 값을 확인해보면 브라우저 정보가 다르게 나온다.

이는 웹 서비스에서 HTTP 요청을 보냈기 때문이다.

이처럼 이용자가 웹 서비스에서 사용하는 마이크로서비스 API 주소를 알아내고 image_url에 주소를 전달하면

외부에서 직접 접근할 수 없는 서비스의 기능을 임의로 사용할 수 있따.

 

 

 

ex) 웹 서비스의 요청 URL에 이용자의 입력값이 포함되어 있은 경우

INTERNAL_API = "http://api.internal/"
# INTERNAL_API = "http://172.17.0.3/"

@app.route("/v1/api/user/information")
def user_info():
	user_idx = request.args.get("user_idx", "")
	response = requests.get(f"{INTERNAL_API}/user/{user_idx}")

@app.route("/v1/api/user/search")
def user_search():
	user_name = request.args.get("user_name", "")
	user_type = "public"
	response = requests.get(f"{INTERNAL_API}/user/search?user_name={user_name}&user_type={user_type}")

코드를 살펴보면 2가지 엔드포인트가 존재한다.

1)이용자가 전달한 user_idx 값을 내부 API의 URL 경로로 사용한다.

http://x.x.x.x/v1/api/user/information?user_idx=1

이용자가 위와 같이 user_idx를 1로 설정하고 요청을 보내면 웹 서비스는 다음과 같은 주소에 요청을 보낸다.

http://api.internal/user/1

 

2) user_search

이용자가 전달한 user_name 값을 내부 API의 쿼리로 사용한다.

http://x.x.x.x/v1/api/user/search?user_name=hello

user_name을 “hello”로 설정하고 요청을 보내면 웹 서비스는 다음과 같은 주소에 요청을 보낸다.

http://api.internal/user/search?user_name=hello&user_type=public

 

문제점은 웹 서비스가 요청하는 URL에 이용자의 입력값이 포함되면 요청을 변조할 수 있다.

이용자의 입력값 중 URL의 구성 요소 문자를 삽입하면 API 경로를 조작할 수 있다.

user_idx에 ../search를 입력할 경우 웹 서비스는 다음과 같은 URL에 요청을 보낸다.

http://api.internal/search

..는 상위 경로로 이동하기 위한 구분자로, 해당 문자로 요청을 보내는 경로를 조작할 수 있다.

이 외에도 #을 이용하여 뒷부분을 주석처리할 수 있다.

 

출처 및 참고 https://dreamhack.io/

 

해커들의 놀이터, Dreamhack

해킹과 보안에 대한 공부를 하고 싶은 학생, 안전한 코드를 작성하고 싶은 개발자, 보안 지식과 실력을 업그레이드 시키고 싶은 보안 전문가까지 함께 공부하고 연습하며 지식을 나누고 실력 향

dreamhack.io

 

 

위에서 배운 사항들을 토대로 portswigger에서 제공하는 문제를 풀이해보자.

해당 문제는 다음 링크에서 풀어볼 수 있다.

https://portswigger.net/web-security/ssrf/lab-basic-ssrf-against-localhost

 

Lab: Basic SSRF against the local server | Web Security Academy

This lab has a stock check feature which fetches data from an internal system. To solve the lab, change the stock check URL to access the admin interface at ...

portswigger.net

해당 문제를 들어가면 다음과 같은 페이지가 나온다.

 

해당 페이지에 URL에 /admin을 추가한다.

그러면 위와 같이 내부 네트워크에서만 접속할 수 있다고 출력된다. 그럼 SSRF 공격을 위해서 api를 찾아보자.

우선 홈으로 돌아가서 하나의 페이지를 골라 들어가야한다.

하단에 Check stock을 클릭하고 burf suite를 확인해본다.

 

그럼 하단에 다음과 같이 출력된다.

이를 디코딩하면 다음과 같이 출력된다.

해당 URL을 localhost의 admin으로 바꾸고 값을 보내봐야한다.

 

이하부터 접속이 불가하여 텍스트로 대체하여 설명한다.

위와 같이 출력되고 localhost/admin 으로 이동하면 두가지 계정이 출력된다.

이 중 문제에서 원하는 계정을 삭제하려면 delete?username = carlos 라고 입력한다.

그럼 내부에서 계정을 삭제하라는 명령을 보낸것처럼 되기떄문에 계정이 삭제된다.