ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • <WEB Hacking> ServerSide: SSRF
    WEB Hacking 2022. 1. 5. 11:46

    ■SSRF(Server-Side Request Forgery)

    웹 서비스는 외부에서 접근할 수 없는 내부망의 기능을 사용할 때가 있다. 이 서비스는 관리자만 이용해야 하므로 외부에서 접근할 수 없는 내부망에 위치한다. 외부에서 직접 접근할 수 없지만, 웹 서비스는 내부망 서비스와 통신할 수 있다.

    만약 공격자가 SSRF 취약점을 통해 웹 서비스의 권한으로 요청을 보낼 수 있다면, 외부에서 간접적으로 내부망 서비스를 이용할 수 있게된다.

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

    ex) 웹 서비스가 공격자가 입력한 URL에 요청을 보내는 경우

        URL에 공격자의 번호가 사용되는 경우

        공격자가 입력한 값이 HTTP Body에 포함되는 경우

     

    ■웹 서비스가 공격자가 입력한 URL에 요청을 보내는 경우

    from flask import Flask, request
    import requests
    
    app = Flask(__name__)
    
    @app.route("/image_downloader")
    def image_downloader():
        image_url = request.args.get("image_url", "")
        response = requests.get(image_url)
        return (response.content, 200, {"Content-Type": response.headers.get("Content-Type", "")})
        
    @app.route("/request_info")
    def request_info():
        return request.user_agent.string
        
    app.run(host="127.0.0.1", port=8000)
    # 출처: https://dreamhack.io/learn/190#7

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

    request_info 페이지는 웹 페이지에 접속한 User-Agent를 반환한다.

     

    image_downloader 페이지에서 image_url에 request_info 페이지 경로를 입력하면

    이용자가 웹 서비스에서 사용한 마이크로서비스의 API 주소를 반환한다.

    즉, image_url에 주소를 전달함으로서 외부에서 직접 접근할 수 없는 마이크로서비스의 기능을 임의로 사용할 수 있다.

     

    ■URL에 공격자의 입력값이 포함되는 경우

    INTERNAL_API = "http://api.internal/"
    
    @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 = reqeust.args.get("user_name", "")
        user_type = "public"
        response = requests.get(f"{INTERNAL_API}/user/search?username={user_name}&user_type={user_type}")
        
    # 출처: https://dreamhack.io/learn/190#9

    user_info() 함수는 user_idx 값을 내부 API의 URL 경로로 사용한다.

    user_search() 함수는 이용자가 입력한 user_name 값을 내부 API의 쿼리로 사용한다.

     

    만약, 이용자의 입력값에 URL의 구성 요소 문자를 사용하면 API 경로를 조작할 수 있다.

    ex) user_info()에서 user_idx의 값으로 "../search"를 입력하면, 웹 서비스는 "http://api.internal/user/search"에 요청을 보낸다.

     

    ex) user_search()에서 user_name의 값으로 "woong&user_type=private#"를 입력하면, 웹 서비스는 "http://api.internal/search?user_name=woong&user_type=private#&user_type=public"을 요청한다.

     

    ■공격자가 입력한 값이 HTTP Body에 포함되는 경우

    from flask import Flask, request, session
    import requests
    from os import urandom
    
    app = Flask(__name__)
    app.secret_key = urandom(32)
    
    INTERNAL_API = "http://127.0.0.1:8000/"
    header = {"Content_Type":"application/x-www-form-urlencoded"}
    
    @app.route("v1/api/board/write", methods=["POST"])
    def board_write():
        session["idx"] = "guest"
        title = request.form.get("title", "")
        body = request.form.get("body", "")
        data = f"title={title}&body={body}&user={session['idx']}"
        response = requests.post(f"{INTERNAL_API}/board/write", headers=header, data=data)
        retrun response.content
        
    @app.route("/board/write", methods=["POST"])
    def internal_board_write():
        title = request.form.get("title", "")
        body = request.form.get("body", "")
        user = request.form.get("user", "")
        info = {"title" : title,
                "body" : body,
                "user" : user
               }
        return info
        
    @app.route("/")
    def index():
        return """
            <form action="/v1/api/board/write" method="POST">
                <input type="text" placeholder="title" name="title"/><br/>
                <input type="text" placeholder="body" name="body"/><br/>
                <input type="submit"/>
            </form>
        """
        
    app.run(host="127.0.0.1", port=8000, debug=True)
    # 출처: https://dreamhack.io/learn/190#12

    board_write() 함수는 이용자의 입력값을 HTTP Body에 포함하고 내부 API로 요청을 보낸다. 전송할 데이터를 구성할 때 세션 정보를 "guest" 계정으로 설정한다.

    internal_board_write() 함수는 board_write() 함수에서 요청하는 내부 API를 구현한 기능으로, 전달된 title, body, user를 JSON 형식으로 변환하고 반환한다.

    index() 함수는 board_write 기능을 호출하기 위한 인덱스 페이지다.

     

    "http://127.0.0.1:8000"에 접속하면 title과 body를 입력하는 페이지가 표시된다.

    입력창에 값을 입력하고 제출하면 다음과 같은 응답을 반환한다.

    {"body": "body", "title": "title", "user": "guest"}

    요청을 전송할 때 세션 정보를 "guest"로 설정했기 때문에 "user"가 "guest"인 것을 확인할 수 있다.

    내부 API로 요청을 보내기 전에 다음과 같이 데이터를 구성하는 것을 확인할 수 있다.

    data = f"title={title}&body={body}&user={session['idx']}

    데이터를 구성할 때 이용자의 입력값인 title, body, user의 값을 파라미터 형식으로 설정한다.

    이로 인해 이용자가 URL 파라미터를 구분하는 '&'를 사용하면 data의 값을 변조할 수 있다.

    title에 title&user=admin을 입력하면 data는 다음과 같이 구성된다.

    title=title&user=admin&body=body&user=guest

    따라서, 실행 결과를 확인하면

    {"body": "body", "title": "title", "user": "admin"}

    'WEB Hacking' 카테고리의 다른 글

    <WEB Hacking> LFI  (0) 2022.01.12
    <WEB Hacking> File Vulnerability  (0) 2021.12.23
    <WEB Hacking> Command Injection  (0) 2021.12.22
    <WEB Hacking> NoSQL Injection  (0) 2021.12.20
    <WEB Hacking> SQL Injection  (0) 2021.12.20
Designed by Tistory.