[dreamhack] EZ_command_injection

IMKYU·2024년 11월 24일

문제

소스코드

#app.py
#!/usr/bin/env python3
import subprocess
import ipaddress
from flask import Flask, request, render_template

app = Flask(__name__)

@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

@app.route('/ping', methods=['GET'])
def ping():
    host = request.args.get('host', '')
    try:
        addr = ipaddress.ip_address(host)
    except ValueError:
        error_msg = 'Invalid IP address'
        print(error_msg)
        return render_template('index.html', result=error_msg)

    cmd = f'ping -c 3 {addr}'
    try:
        output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=8)
        return render_template('index.html', result=output.decode('utf-8'))
    except subprocess.TimeoutExpired:
        error_msg = 'Timeout!!!!'
        print(error_msg)
        return render_template('index.html', result=error_msg)
    except subprocess.CalledProcessError:
        error_msg = 'An error occurred while executing the command'
        print(error_msg)
        return render_template('index.html', result=error_msg)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)

문제설명

  • 소스코드를 보면 /ping 페이지에서 shell에서 ping를 하는 것을 볼 수 있다.\

  • 그럼 ;ls를 사용하면 바로 풀리겠다.

    • 실패
    • 원인
      @app.route('/ping', methods=['GET'])
      def ping():
          host = request.args.get('host', '')
          try:
              addr = ipaddress.ip_address(host)
          except ValueError:
              error_msg = 'Invalid IP address'
              print(error_msg)
              return render_template('index.html', result=error_msg)
          cmd = f'ping -c 3 {addr}'
    • 코드를 보면 host를 ipaddress.ip_address함수의 인자로 사용해서 반환값을 addr에 담아 ping을 실행하고 있다.
      • ipaddress.ip_address 함수는 올바른 IPv4, IPv6인지 자동으로 판별한다.
      • 따라서 내가 입력한 ;ls는 올바른 ip주소가 아니기 때문에 예외처리 되었다.
  • ipaddress.ip_address를 우회하기

    • IPv6 주소는 스코프 아이디(scope ID)가 있는데 스코프 아이디는 로컬 네트워크에서 사용되는 식별자이다

    • 여기서 lo, eth0, eth1, loopback0, eth2, eth3가 스코프 아이디이다.

    • 스코프 아이디가 사용되는 이유는

      컴퓨터는 여러 개의 서로 다른 스코프의 링크-로컬 주소를 가질 수 있다. 스코프 아이디는 각 주소가 어느 스코프를 위한 것인지를 나타낸다. 예를 들어보자. 컴퓨터가 두 개의 NIC(네트워크 인터페이스 카드)를 갖고 있고 다른 네트워크의 링크-로컬 주소를 가지고 있다. 무언가를 fe80 으로 시작하는 또 다른 주소로 보내려고 한다면, 컴퓨터는 어떤 NIC 를 통해서 보내야 할 지 어떻게 구별할 것인가?

      출처:

      https://daewonyoon.tistory.com/182

      [알락블록:티스토리]

    • https://github.com/python/cpython/blob/main/Lib/ipaddress.py

      def _split_scope_id(ip_str):
              """Helper function to parse IPv6 string address with scope id.
      
              See RFC 4007 for details.
      
              Args:
                  ip_str: A string, the IPv6 address.
      
              Returns:
                  (addr, scope_id) tuple.
      
              """
              addr, sep, scope_id = ip_str.partition('%')
              if not sep:
                  scope_id = None
              elif not scope_id or '%' in scope_id:
                  raise AddressValueError('Invalid IPv6 address: "%r"' % ip_str)
              return addr, scope_id
    • 이 코드에서 %를 기준으로 나눈다.

      • addr는 %의 앞부분 sep는 % scope_id는 %이후
      • sep 가 없다면 scope_id가 입력되지 않은 것이다.
      • scope_id가 없고 scope_id에 %가 있다면 에러
    • scope_id에 %를 제외하고 아무 문자열이 와도 상관이 없다.

공격

  • 적당한 ipv6주소 + % + ; + shell명령어를 host에 넣으면 성공할 것 같다.

  • 성공

profile
아무것도 안 한 거랑 다를께 없잖아??

0개의 댓글