mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
318 lines
18 KiB
Markdown
318 lines
18 KiB
Markdown
# Nginx
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
|
||
## Missing root location <a href="#missing-root-location" id="missing-root-location"></a>
|
||
|
||
Nginx 서버를 구성할 때, **root directive**는 파일이 제공되는 기본 디렉토리를 정의함으로써 중요한 역할을 합니다. 아래의 예를 고려해 보십시오:
|
||
```bash
|
||
server {
|
||
root /etc/nginx;
|
||
|
||
location /hello.txt {
|
||
try_files $uri $uri/ =404;
|
||
proxy_pass http://127.0.0.1:8080/;
|
||
}
|
||
}
|
||
```
|
||
이 구성에서 `/etc/nginx`는 루트 디렉토리로 지정됩니다. 이 설정은 `/hello.txt`와 같은 지정된 루트 디렉토리 내의 파일에 대한 접근을 허용합니다. 그러나 특정 위치(`location /hello.txt`)만 정의되어 있다는 점이 중요합니다. 루트 위치(`location / {...}`)에 대한 구성은 없습니다. 이 누락은 루트 지시어가 전역적으로 적용됨을 의미하며, 루트 경로 `/`에 대한 요청이 `/etc/nginx` 아래의 파일에 접근할 수 있게 합니다.
|
||
|
||
이 구성에서 중요한 보안 고려 사항이 발생합니다. `GET /nginx.conf`와 같은 간단한 `GET` 요청은 `/etc/nginx/nginx.conf`에 위치한 Nginx 구성 파일을 제공함으로써 민감한 정보를 노출할 수 있습니다. 루트를 `/etc`와 같은 덜 민감한 디렉토리로 설정하면 이 위험을 완화할 수 있지만, 여전히 다른 중요한 파일, 다른 구성 파일, 접근 로그 및 HTTP 기본 인증에 사용되는 암호화된 자격 증명에 대한 의도치 않은 접근을 허용할 수 있습니다.
|
||
|
||
## Alias LFI Misconfiguration <a href="#alias-lfi-misconfiguration" id="alias-lfi-misconfiguration"></a>
|
||
|
||
Nginx의 구성 파일에서는 "location" 지시어에 대한 면밀한 검토가 필요합니다. Local File Inclusion (LFI)로 알려진 취약점은 다음과 유사한 구성을 통해 우연히 도입될 수 있습니다:
|
||
```
|
||
location /imgs {
|
||
alias /path/images/;
|
||
}
|
||
```
|
||
이 구성은 서버가 `/imgs../flag.txt`와 같은 요청을 의도된 디렉토리 외부의 파일에 접근하려는 시도로 해석하기 때문에 LFI 공격에 취약합니다. 이는 실제로 `/path/images/../flag.txt`로 해결됩니다. 이 결함은 공격자가 웹을 통해 접근할 수 없어야 하는 서버의 파일 시스템에서 파일을 검색할 수 있게 합니다.
|
||
|
||
이 취약점을 완화하기 위해 구성은 다음과 같이 조정되어야 합니다:
|
||
```
|
||
location /imgs/ {
|
||
alias /path/images/;
|
||
}
|
||
```
|
||
더 많은 정보: [https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/](https://www.acunetix.com/vulnerabilities/web/path-traversal-via-misconfigured-nginx-alias/)
|
||
|
||
Accunetix 테스트:
|
||
```
|
||
alias../ => HTTP status code 403
|
||
alias.../ => HTTP status code 404
|
||
alias../../ => HTTP status code 403
|
||
alias../../../../../../../../../../../ => HTTP status code 400
|
||
alias../ => HTTP status code 403
|
||
```
|
||
## Unsafe path restriction <a href="#unsafe-variable-use" id="unsafe-variable-use"></a>
|
||
|
||
다음 페이지를 확인하여 다음과 같은 지시문을 우회하는 방법을 알아보세요:
|
||
```plaintext
|
||
location = /admin {
|
||
deny all;
|
||
}
|
||
|
||
location = /admin/ {
|
||
deny all;
|
||
}
|
||
```
|
||
{{#ref}}
|
||
../../pentesting-web/proxy-waf-protections-bypass.md
|
||
{{#endref}}
|
||
|
||
## 안전하지 않은 변수 사용 / HTTP 요청 분할 <a href="#unsafe-variable-use" id="unsafe-variable-use"></a>
|
||
|
||
> [!CAUTION]
|
||
> 취약한 변수 `$uri`와 `$document_uri`가 있으며, 이를 `$request_uri`로 교체하여 수정할 수 있습니다.
|
||
>
|
||
> 정규 표현식도 취약할 수 있습니다:
|
||
>
|
||
> `location ~ /docs/([^/])? { … $1 … }` - 취약
|
||
>
|
||
> `location ~ /docs/([^/\s])? { … $1 … }` - 취약하지 않음 (공백 확인)
|
||
>
|
||
> `location ~ /docs/(.*)? { … $1 … }` - 취약하지 않음
|
||
|
||
Nginx 구성의 취약점은 아래 예제로 설명됩니다:
|
||
```
|
||
location / {
|
||
return 302 https://example.com$uri;
|
||
}
|
||
```
|
||
HTTP 요청에서 \r (Carriage Return) 및 \n (Line Feed) 문자는 새 줄 문자를 나타내며, 이들의 URL 인코딩 형태는 `%0d%0a`로 표현됩니다. 잘못 구성된 서버에 이러한 문자를 포함한 요청(예: `http://localhost/%0d%0aDetectify:%20clrf`)을 보내면 서버는 `Detectify`라는 새 헤더를 발급합니다. 이는 $uri 변수가 URL 인코딩된 새 줄 문자를 디코딩하여 응답에 예상치 못한 헤더가 포함되기 때문입니다:
|
||
```
|
||
HTTP/1.1 302 Moved Temporarily
|
||
Server: nginx/1.19.3
|
||
Content-Type: text/html
|
||
Content-Length: 145
|
||
Connection: keep-alive
|
||
Location: https://example.com/
|
||
Detectify: clrf
|
||
```
|
||
CRLF 주입 및 응답 분할의 위험에 대해 더 알아보세요 [https://blog.detectify.com/2019/06/14/http-response-splitting-exploitations-and-mitigations/](https://blog.detectify.com/2019/06/14/http-response-splitting-exploitations-and-mitigations/).
|
||
|
||
또한 이 기술은 [**이 강의에서 설명됩니다**](https://www.youtube.com/watch?v=gWQyWdZbdoY&list=PL0xCSYnG_iTtJe2V6PQqamBF73n7-f1Nr&index=77) 취약한 예제와 탐지 메커니즘과 함께. 예를 들어, 블랙박스 관점에서 이 잘못된 구성을 탐지하기 위해 다음 요청을 사용할 수 있습니다:
|
||
|
||
- `https://example.com/%20X` - 모든 HTTP 코드
|
||
- `https://example.com/%20H` - 400 잘못된 요청
|
||
|
||
취약한 경우, 첫 번째는 "X"가 모든 HTTP 메서드이므로 반환되고, 두 번째는 H가 유효한 메서드가 아니므로 오류가 반환됩니다. 따라서 서버는 다음과 같은 것을 받게 됩니다: `GET / H HTTP/1.1` 이로 인해 오류가 발생합니다.
|
||
|
||
또 다른 탐지 예는 다음과 같습니다:
|
||
|
||
- `http://company.tld/%20HTTP/1.1%0D%0AXXXX:%20x` - 모든 HTTP 코드
|
||
- `http://company.tld/%20HTTP/1.1%0D%0AHost:%20x` - 400 잘못된 요청
|
||
|
||
그 강의에서 발견된 취약한 구성 중 일부는 다음과 같습니다:
|
||
|
||
- 최종 URL에서 **`$uri`**가 그대로 설정된 방법을 주목하세요.
|
||
```
|
||
location ^~ /lite/api/ {
|
||
proxy_pass http://lite-backend$uri$is_args$args;
|
||
}
|
||
```
|
||
- 다시 **`$uri`**가 URL에 있는 것을 주목하세요 (이번에는 매개변수 안에 있습니다)
|
||
```
|
||
location ~ ^/dna/payment {
|
||
rewrite ^/dna/([^/]+) /registered/main.pl?cmd=unifiedPayment&context=$1&native_uri=$uri break;
|
||
proxy_pass http://$back;
|
||
```
|
||
- 이제 AWS S3에서
|
||
```
|
||
location /s3/ {
|
||
proxy_pass https://company-bucket.s3.amazonaws.com$uri;
|
||
}
|
||
```
|
||
### Any variable
|
||
|
||
**사용자 제공 데이터**가 특정 상황에서 **Nginx 변수**로 처리될 수 있다는 것이 발견되었습니다. 이 행동의 원인은 다소 불분명하지만, 드물지 않으며 검증하기도 간단하지 않습니다. 이 이상 현상은 HackerOne의 보안 보고서에서 강조되었으며, [여기서](https://hackerone.com/reports/370094) 확인할 수 있습니다. 오류 메시지에 대한 추가 조사는 [Nginx 코드베이스의 SSI 필터 모듈](https://github.com/nginx/nginx/blob/2187586207e1465d289ae64cedc829719a048a39/src/http/modules/ngx_http_ssi_filter_module.c#L365) 내에서 발생하는 것을 확인하게 되었으며, 서버 사이드 인클루드(SSI)가 근본 원인으로 지목되었습니다.
|
||
|
||
이 **잘못된 구성**을 **탐지하기 위해**, 다음 명령을 실행할 수 있으며, 이는 변수 출력을 테스트하기 위해 referer 헤더를 설정하는 것을 포함합니다:
|
||
```bash
|
||
$ curl -H ‘Referer: bar’ http://localhost/foo$http_referer | grep ‘foobar’
|
||
```
|
||
이 잘못된 구성에 대한 스캔 결과, 사용자가 Nginx 변수를 출력할 수 있는 여러 사례가 발견되었습니다. 그러나 취약한 사례의 수가 감소한 것은 이 문제를 패치하기 위한 노력이 어느 정도 성공적이었다는 것을 시사합니다.
|
||
|
||
### $URI$ARGS 변수를 사용한 try_files
|
||
|
||
다음 Nginx 잘못된 구성은 LFI 취약점으로 이어질 수 있습니다:
|
||
```
|
||
location / {
|
||
try_files $uri$args $uri$args/ /index.html;
|
||
}
|
||
```
|
||
우리 구성에서는 지정된 순서로 파일의 존재 여부를 확인하는 데 사용되는 `try_files` 지시어가 있습니다. Nginx는 찾은 첫 번째 파일을 제공합니다. `try_files` 지시어의 기본 구문은 다음과 같습니다:
|
||
```
|
||
try_files file1 file2 ... fileN fallback;
|
||
```
|
||
Nginx는 지정된 순서로 각 파일의 존재 여부를 확인합니다. 파일이 존재하면 즉시 제공됩니다. 지정된 파일이 모두 존재하지 않으면 요청은 대체 옵션으로 전달되며, 이는 다른 URI 또는 특정 오류 페이지일 수 있습니다.
|
||
|
||
그러나 이 지시문에서 `$uri$args` 변수를 사용할 때, Nginx는 요청 URI와 쿼리 문자열 인수를 결합하여 일치하는 파일을 찾으려고 시도합니다. 따라서 이 구성을 악용할 수 있습니다:
|
||
```
|
||
http {
|
||
server {
|
||
root /var/www/html/public;
|
||
|
||
location / {
|
||
try_files $uri$args $uri$args/ /index.html;
|
||
}
|
||
}
|
||
}
|
||
```
|
||
다음 페이로드:
|
||
```
|
||
GET /?../../../../../../../../etc/passwd HTTP/1.1
|
||
Host: example.com
|
||
```
|
||
우리의 페이로드를 사용하여 Nginx 구성에서 정의된 루트 디렉토리를 탈출하고 `/etc/passwd` 파일을 로드할 것입니다. 디버그 로그에서 Nginx가 파일을 시도하는 방식을 관찰할 수 있습니다:
|
||
```
|
||
...SNIP...
|
||
|
||
2025/07/11 15:49:16 [debug] 79694#79694: *4 trying to use file: "/../../../../../../../../etc/passwd" "/var/www/html/public/../../../../../../../../etc/passwd"
|
||
2025/07/11 15:49:16 [debug] 79694#79694: *4 try file uri: "/../../../../../../../../etc/passwd"
|
||
|
||
...SNIP...
|
||
|
||
2025/07/11 15:49:16 [debug] 79694#79694: *4 http filename: "/var/www/html/public/../../../../../../../../etc/passwd"
|
||
|
||
...SNIP...
|
||
|
||
2025/07/11 15:49:16 [debug] 79694#79694: *4 HTTP/1.1 200 OK
|
||
|
||
```
|
||
Nginx의 위에서 언급한 구성에 대한 PoC:
|
||

|
||
|
||
## 원시 백엔드 응답 읽기
|
||
|
||
Nginx는 `proxy_pass`를 통해 백엔드에서 생성된 오류 및 HTTP 헤더를 가로채는 기능을 제공하여 내부 오류 메시지와 헤더를 숨기도록 설계되었습니다. 이는 Nginx가 백엔드 오류에 대한 사용자 정의 오류 페이지를 제공함으로써 이루어집니다. 그러나 Nginx가 유효하지 않은 HTTP 요청을 처리할 때 문제가 발생합니다. 이러한 요청은 수신된 대로 백엔드로 전달되며, 백엔드의 원시 응답은 Nginx의 개입 없이 클라이언트에게 직접 전송됩니다.
|
||
|
||
uWSGI 애플리케이션을 포함한 예시 시나리오를 고려해 보십시오:
|
||
```python
|
||
def application(environ, start_response):
|
||
start_response('500 Error', [('Content-Type', 'text/html'), ('Secret-Header', 'secret-info')])
|
||
return [b"Secret info, should not be visible!"]
|
||
```
|
||
이를 관리하기 위해 Nginx 구성에서 특정 지시어가 사용됩니다:
|
||
```
|
||
http {
|
||
error_page 500 /html/error.html;
|
||
proxy_intercept_errors on;
|
||
proxy_hide_header Secret-Header;
|
||
}
|
||
```
|
||
- [**proxy_intercept_errors**](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_intercept_errors): 이 지시어는 Nginx가 상태 코드가 300보다 큰 백엔드 응답에 대해 사용자 정의 응답을 제공할 수 있도록 합니다. 이는 예시로 든 uWSGI 애플리케이션의 경우 `500 Error` 응답이 Nginx에 의해 가로채지고 처리되도록 보장합니다.
|
||
- [**proxy_hide_header**](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_hide_header): 이름에서 알 수 있듯이, 이 지시어는 클라이언트로부터 지정된 HTTP 헤더를 숨겨 개인 정보 보호 및 보안을 강화합니다.
|
||
|
||
유효한 `GET` 요청이 이루어지면 Nginx는 이를 정상적으로 처리하여 비밀 헤더를 노출하지 않고 표준 오류 응답을 반환합니다. 그러나 유효하지 않은 HTTP 요청은 이 메커니즘을 우회하여 비밀 헤더와 오류 메시지를 포함한 원시 백엔드 응답이 노출됩니다.
|
||
|
||
## merge_slashes를 off로 설정
|
||
|
||
기본적으로 Nginx의 **`merge_slashes` 지시어**는 **`on`**으로 설정되어 있어 URL의 여러 개의 슬래시를 하나의 슬래시로 압축합니다. 이 기능은 URL 처리를 간소화하지만, Nginx 뒤에 있는 애플리케이션에서 특히 로컬 파일 포함(LFI) 공격에 취약한 경우 취약점을 숨길 수 있습니다. 보안 전문가 **Danny Robinson과 Rotem Bar**는 Nginx가 리버스 프록시로 작동할 때 이 기본 동작과 관련된 잠재적 위험을 강조했습니다.
|
||
|
||
이러한 위험을 완화하기 위해, 이러한 취약점에 취약한 애플리케이션에 대해 **`merge_slashes` 지시어를 끄는 것이 권장됩니다**. 이는 Nginx가 URL 구조를 변경하지 않고 애플리케이션에 요청을 전달하도록 보장하여 기본 보안 문제를 숨기지 않도록 합니다.
|
||
|
||
자세한 내용은 [Danny Robinson과 Rotem Bar](https://medium.com/appsflyer/nginx-may-be-protecting-your-applications-from-traversal-attacks-without-you-even-knowing-b08f882fd43d)를 확인하세요.
|
||
|
||
### **Maclicious Response Headers**
|
||
|
||
[**이 글**](https://mizu.re/post/cors-playground)에서 보여준 바와 같이, 웹 서버의 응답에 존재하는 특정 헤더는 Nginx 프록시의 동작을 변경할 수 있습니다. 이들은 [**문서에서 확인할 수 있습니다**](https://www.nginx.com/resources/wiki/start/topics/examples/x-accel/):
|
||
|
||
- `X-Accel-Redirect`: Nginx가 요청을 지정된 위치로 내부 리디렉션하도록 지시합니다.
|
||
- `X-Accel-Buffering`: Nginx가 응답을 버퍼링할지 여부를 제어합니다.
|
||
- `X-Accel-Charset`: X-Accel-Redirect를 사용할 때 응답의 문자 집합을 설정합니다.
|
||
- `X-Accel-Expires`: X-Accel-Redirect를 사용할 때 응답의 만료 시간을 설정합니다.
|
||
- `X-Accel-Limit-Rate`: X-Accel-Redirect를 사용할 때 응답의 전송 속도를 제한합니다.
|
||
|
||
예를 들어, 헤더 **`X-Accel-Redirect`**는 Nginx에서 내부 **리디렉션**을 발생시킵니다. 따라서 **`root /`**와 같은 설정을 가진 Nginx 구성과 웹 서버의 응답에 **`X-Accel-Redirect: .env`**가 포함되면 Nginx는 **`/.env`**의 내용을 전송하게 됩니다 (경로 탐색).
|
||
|
||
### **Map Directive의 기본값**
|
||
|
||
**Nginx 구성**에서 `map` 지시어는 종종 **권한 제어**에서 중요한 역할을 합니다. 일반적인 실수는 **기본** 값을 지정하지 않는 것으로, 이는 무단 접근으로 이어질 수 있습니다. 예를 들어:
|
||
```yaml
|
||
http {
|
||
map $uri $mappocallow {
|
||
/map-poc/private 0;
|
||
/map-poc/secret 0;
|
||
/map-poc/public 1;
|
||
}
|
||
}
|
||
```
|
||
|
||
```yaml
|
||
server {
|
||
location /map-poc {
|
||
if ($mappocallow = 0) {return 403;}
|
||
return 200 "Hello. It is private area: $mappocallow";
|
||
}
|
||
}
|
||
```
|
||
`default`가 없으면, **악의적인 사용자**는 `/map-poc` 내에서 **정의되지 않은 URI**에 접근하여 보안을 우회할 수 있습니다. [Nginx 매뉴얼](https://nginx.org/en/docs/http/ngx_http_map_module.html)에서는 이러한 문제를 피하기 위해 **기본값**을 설정할 것을 권장합니다.
|
||
|
||
### **DNS 스푸핑 취약점**
|
||
|
||
특정 조건에서 Nginx에 대한 DNS 스푸핑이 가능합니다. 공격자가 Nginx에서 사용하는 **DNS 서버**를 알고 그 DNS 쿼리를 가로챌 수 있다면, DNS 레코드를 스푸핑할 수 있습니다. 그러나 Nginx가 DNS 해석을 위해 **localhost (127.0.0.1)**를 사용하도록 구성된 경우, 이 방법은 효과적이지 않습니다. Nginx는 다음과 같이 DNS 서버를 지정할 수 있습니다:
|
||
```yaml
|
||
resolver 8.8.8.8;
|
||
```
|
||
### **`proxy_pass` 및 `internal` 지시어**
|
||
|
||
**`proxy_pass`** 지시어는 요청을 내부 또는 외부의 다른 서버로 리디렉션하는 데 사용됩니다. **`internal`** 지시어는 특정 위치가 Nginx 내에서만 접근 가능하도록 보장합니다. 이러한 지시어 자체는 취약점이 아니지만, 보안 누수를 방지하기 위해 구성에 대한 신중한 검토가 필요합니다.
|
||
|
||
## proxy_set_header Upgrade & Connection
|
||
|
||
nginx 서버가 Upgrade 및 Connection 헤더를 전달하도록 구성된 경우, [**h2c Smuggling 공격**](../../pentesting-web/h2c-smuggling.md)를 수행하여 보호된/내부 엔드포인트에 접근할 수 있습니다.
|
||
|
||
> [!CAUTION]
|
||
> 이 취약점은 공격자가 **`proxy_pass` 엔드포인트와 직접 연결을 설정할 수 있게 합니다** (`http://backend:9999`의 경우) 이 콘텐츠는 nginx에 의해 확인되지 않습니다.
|
||
|
||
취약한 구성의 예로 `/flag`를 훔치는 방법은 [여기](https://bishopfox.com/blog/h2c-smuggling-request)에서 확인할 수 있습니다:
|
||
```
|
||
server {
|
||
listen 443 ssl;
|
||
server_name localhost;
|
||
|
||
ssl_certificate /usr/local/nginx/conf/cert.pem;
|
||
ssl_certificate_key /usr/local/nginx/conf/privkey.pem;
|
||
|
||
location / {
|
||
proxy_pass http://backend:9999;
|
||
proxy_http_version 1.1;
|
||
proxy_set_header Upgrade $http_upgrade;
|
||
proxy_set_header Connection $http_connection;
|
||
}
|
||
|
||
location /flag {
|
||
deny all;
|
||
}
|
||
```
|
||
> [!WARNING]
|
||
> `proxy_pass`가 `http://backend:9999/socket.io`와 같은 특정 **경로**를 가리키고 있더라도, 연결은 `http://backend:9999`로 설정되므로 **내부 엔드포인트 내의 다른 경로에 연락할 수 있습니다. 따라서 proxy_pass의 URL에 경로가 지정되어 있는지는 중요하지 않습니다.**
|
||
|
||
## 직접 해보세요
|
||
|
||
Detectify는 이 기사에서 논의된 몇 가지 잘못된 구성으로 자신의 취약한 Nginx 테스트 서버를 설정하기 위해 Docker를 사용할 수 있는 GitHub 리포지토리를 만들었습니다. 직접 찾아보세요!
|
||
|
||
[https://github.com/detectify/vulnerable-nginx](https://github.com/detectify/vulnerable-nginx)
|
||
|
||
## 정적 분석 도구
|
||
|
||
### [GIXY](https://github.com/yandex/gixy)
|
||
|
||
Gixy는 Nginx 구성을 분석하는 도구입니다. Gixy의 주요 목표는 보안 잘못된 구성을 방지하고 결함 탐지를 자동화하는 것입니다.
|
||
|
||
### [Nginxpwner](https://github.com/stark0de/nginxpwner)
|
||
|
||
Nginxpwner는 일반적인 Nginx 잘못된 구성 및 취약점을 찾기 위한 간단한 도구입니다.
|
||
|
||
## 참고자료
|
||
|
||
- [**https://blog.detectify.com/2020/11/10/common-nginx-misconfigurations/**](https://blog.detectify.com/2020/11/10/common-nginx-misconfigurations/)
|
||
- [**http://blog.zorinaq.com/nginx-resolver-vulns/**](http://blog.zorinaq.com/nginx-resolver-vulns/)
|
||
- [**https://github.com/yandex/gixy/issues/115**](https://github.com/yandex/gixy/issues/115)
|
||
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|