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.

Advertisements

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s