web-ssrf


/img_viewer

  • /img_viewer 경로로 POST 요청을 보내면, url 파라미터에 담긴 값이 서버로 전달되는데요.
  • app.py 33-42라인에서 어떤 값을 반환할 지 판단하고 “img_viewer_html” 소스로 “img” 값을 전달합니다.

  • 전달된 이미지 정보는 아래와 같이 “img_viewer” 경로에 반환되고 화면에 출력됩니다.

  • 다시 “app.py” 소스를 살펴보겠습니다.
    • (1) url 파라미터에 ”/” 값만 입력되면 “index.html” 페이지의 소스가 반환되고,
    • (2) url 파라미터의 “urlp.netloc” 값이 localhost || 127.0.0.1 이면 error.png 파일이 반환됩니다.
  • 따라서, SSRF로 플래그를 출력하기 위해서는 40-42 라인의 try 코드 블록으로 진입해야 합니다.

  • 추가로, 49-52 라인을 보면 1500-1800번 포트 중 특정 포트에서 http 서버가 실행되고 있습니다.
  • http://127.0.0.1:{PORT}/flag.txt 경로로 SSRF 요청을 보내면 플래그 확인이 가능해 보입니다.
    • 그러나 여기서 문제가 하나 있는데요. 36라인을 보면 “127.0.0.1, localhost” 문자열을 필터링 하고 있습니다.
  • requests.get(url) 구조에서 reqeuests 는 아래의 순서로 동작한다고 하는데요.
    • requests > urllib3 > Python socket > OS libc
  • 이 때, Python의 socket.getaddrinfo() 는 아래와 같은 IP 표현을 모두 허용합니다.
IP Address의미
127.0.0.1일반 IPv4
213070643310진수 정수표기
0177.00.00.01OCT(8진수)
0x7F.0x00.0x00.0x01HEX(16진수)
0x7F00000132비트 정수의 hex
127.1축약표현 → 127.0.0.1
127.0.0.257오버플로우 시도도 127.0.1.1 로 처리

  • 위 표를 참고하여 “127.0.0.1” IP 주소를 우회하여 서버에 요청을 보내보겠습니다.
    • 1500-1800번 포트 중 유효한 포트는 1729번 포트임을 확인했습니다.
    • 요청도 정상적으로 보내지고 있네요.

  • 그럼 이제, 1726번 포트로 플래그를 요청해보겠습니다.
  • base64 인코딩 된 값이 반환되고 있는데요. 이 값을 디코딩 해보겠습니다.

  • DH{…} 형태의 플래그 획득에 성공했습니다.

🐛.. 🐛.. 🐛..