幾道web題簡單總結

拖了好長時間,總結一下這一段時間作的幾道值得記錄一下的題目,有的沒作出來,可是學習到了新的東西php

1.homebrew event loop

  ddctf的一道題目,學到了python eval函數的用法,首先分析題目:

html

# -*- encoding: utf-8 -*-
# written in python 2.7
__author__ = 'garzon'

from flask import Flask, session, request, Response
import urllib

app = Flask(__name__)
app.secret_key = '*********************'  # censored
url_prefix = '/d5af31f99147e857'


def FLAG():
    return 'FLAG_is_here_but_i_wont_show_you'  # censored


def trigger_event(event):
    session['log'].append(event)
    if len(session['log']) > 5: session['log'] = session['log'][-5:]
    if type(event) == type([]):
        request.event_queue += event
    else:
        request.event_queue.append(event)


def get_mid_str(haystack, prefix, postfix=None):
    haystack = haystack[haystack.find(prefix) + len(prefix):]
    if postfix is not None:
        haystack = haystack[:haystack.find(postfix)]
    return haystack


class RollBackException: pass


def execute_event_loop():
    valid_event_chars = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        event = request.event_queue[0]  # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        request.event_queue = request.event_queue[1:]
        if not event.startswith(('action:', 'func:')): continue
        for c in event:
            if c not in valid_event_chars: break
        else:
            is_action = event[0] == 'a'
            action = get_mid_str(event, ':', ';')  # index
            args = get_mid_str(event, action + ';').split('#') #True#True
            try:
                event_handler = eval(action + ('_handler' if is_action else '_function'))
                ret_val = event_handler(args)
            except RollBackException:
                if resp is None: resp = ''
                resp += 'ERROR! All transactions have been cancelled. <br />'
                resp += '<a href="./?action:view;index">Go back to index.html</a><br />'
                session['num_items'] = request.prev_session['num_items']
                session['points'] = request.prev_session['points']
                break
            except Exception, e:
                if resp is None: resp = ''
                # resp += str(e) # only for debugging
                continue
            if ret_val is not None:
                if resp is None:
                    resp = ret_val
                else:
                    resp += ret_val
    if resp is None or resp == '': resp = ('404 NOT FOUND', 404)
    session.modified = True
    return resp


@app.route(url_prefix + '/')
def entry_point():
    querystring = urllib.unquote(request.query_string)
    request.event_queue = []
    if querystring == '' or (not querystring.startswith('action:')) or len(querystring) > 100:
        querystring = 'action:index;False#False'
    if 'num_items' not in session:
        session['num_items'] = 0
        session['points'] = 3
        session['log'] = []
    request.prev_session = dict(session)
    trigger_event(querystring)
    return execute_event_loop()


# handlers/functions below --------------------------------------

def view_handler(args):
    page = args[0]
    html = ''
    html += '[INFO] you have {} diamonds, {} points now.<br />'.format(session['num_items'], session['points'])
    if page == 'index':
        html += '<a href="./?action:index;True%23False">View source code</a><br />'
        html += '<a href="./?action:view;shop">Go to e-shop</a><br />'
        html += '<a href="./?action:view;reset">Reset</a><br />'
    elif page == 'shop':
        html += '<a href="./?action:buy;1">Buy a diamond (1 point)</a><br />'
    elif page == 'reset':
        del session['num_items']
        html += 'Session reset.<br />'
    html += '<a href="./?action:view;index">Go back to index.html</a><br />'
    return html


def index_handler(args):
    bool_show_source = str(args[0])
    bool_download_source = str(args[1])
    if bool_show_source == 'True':

        source = open('eventLoop.py', 'r')
        html = ''
        if bool_download_source != 'True':
            html += '<a href="./?action:index;True%23True">Download this .py file</a><br />'
            html += '<a href="./?action:view;index">Go back to index.html</a><br />'

        for line in source:
            if bool_download_source != 'True':
                html += line.replace('&', '&amp;').replace('\t', '&nbsp;' * 4).replace(' ', '&nbsp;').replace('<',
                                                                                                              '&lt;').replace(
                    '>', '&gt;').replace('\n', '<br />')
            else:
                html += line
        source.close()

        if bool_download_source == 'True':
            headers = {}
            headers['Content-Type'] = 'text/plain'
            headers['Content-Disposition'] = 'attachment; filename=serve.py'
            return Response(html, headers=headers)
        else:
            return html
    else:
        trigger_event('action:view;index')


def buy_handler(args):
    num_items = int(args[0])
    if num_items <= 0: return 'invalid number({}) of diamonds to buy<br />'.format(args[0])
    session['num_items'] += num_items
    trigger_event(['func:consume_point;{}'.format(num_items), 'action:view;index'])


def consume_point_function(args):
    point_to_consume = int(args[0])
    if session['points'] < point_to_consume: raise RollBackException()
    session['points'] -= point_to_consume


def show_flag_function(args):
    flag = args[0]
    # return flag # GOTCHA! We noticed that here is a backdoor planted by a hacker which will print the flag, so we disabled it.
    return 'You naughty boy! ;) <br />'


def get_flag_handler(args):
    if session['num_items'] >= 5:
        trigger_event('func:show_flag;' + FLAG())  # show_flag_function has been disabled, no worries
    trigger_event('action:view;index')


if __name__ == '__main__':
    app.run(debug=False, host='0.0.0.0')

這道題目首先通讀源碼是必須的,另外一個必需要了解到的出題點在eval()函數這個地方,eval中能夠傳入#來註釋掉後面的部分
python

從上圖能夠看出來,此時eval會忽略掉#後面的全部字符串,以及要作出這道題的另外一個點:mysql

打破程序進行的流程,先加鑽石數量再檢驗錢數,而且能夠給事件傳入一個列表,那麼先加鑽石,在檢驗錢以前去getflag便可,並且這裏會把flag帶到log中去,總之就是在一個正常的處理序列中去插入一個新的事件,由於eval這裏可控,因此剛開始就應該反映到出題點,實際上就是代碼注入linux

在這裏!web

 2.mysql弱口令

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 12/1/2019 2:58 PM
# @Author  : fz
# @Site    : 
# @File    : agent.py
# @Software: PyCharm

import json
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from optparse import OptionParser
from subprocess import Popen, PIPE


class RequestHandler(BaseHTTPRequestHandler):

    def do_GET(self):
        request_path = self.path

        print("\n----- Request Start ----->\n")
        print("request_path :", request_path)
        print("self.headers :", self.headers)
        print("<----- Request End -----\n")

        self.send_response(200)
        self.send_header("Set-Cookie", "foo=bar")
        self.end_headers()

        result = self._func()
        self.wfile.write(json.dumps(result))


    def do_POST(self):
        request_path = self.path

        # print("\n----- Request Start ----->\n")
        print("request_path : %s", request_path)

        request_headers = self.headers
        content_length = request_headers.getheaders('content-length')
        length = int(content_length[0]) if content_length else 0

        # print("length :", length)

        print("request_headers : %s" % request_headers)
        print("content : %s" % self.rfile.read(length))
        # print("<----- Request End -----\n")

        self.send_response(200)
        self.send_header("Set-Cookie", "foo=bar")
        self.end_headers()
        result = self._func()
        self.wfile.write(json.dumps(result))

    def _func(self):
        netstat = Popen(['netstat', '-tlnp'], stdout=PIPE)
        netstat.wait()

        ps_list = netstat.stdout.readlines()
        result = []
        for item in ps_list[2:]:
            tmp = item.split()
            Local_Address = tmp[3]
            Process_name = tmp[6]
            tmp_dic = {'local_address': Local_Address, 'Process_name': Process_name}
            result.append(tmp_dic)
        return result

    do_PUT = do_POST
    do_DELETE = do_GET


def main():
    port = 8123
    print('Listening on localhost:%s' % port)
    server = HTTPServer(('0.0.0.0', port), RequestHandler)
    server.serve_forever()


if __name__ == "__main__":
    parser = OptionParser()
    parser.usage = (
        "Creates an http-server that will echo out any GET or POST parameters, and respond with dummy data\n"
        "Run:\n\n")
    (options, args) = parser.parse_args()

    main()

 這道題主要是來攻擊mysql鏈接的客戶端,這個題目給了agent.py 是用來檢測是否是服務器上存在mysqld進程,而判斷是經過do_get和do_post兩個函數肯定的,這兩個函數都會調用_func函數,返回進程名,而後do_get 和do_post再把_func的返回值輸出,sql

因此只須要讓最後輸出的存在mysqld就好了,而後就能夠在服務器上讀取客戶端的文件。shell

這裏讀取客戶端的.mysql_histoty文件,這個文件存儲了用戶登錄mysql服務器所執行的命令,也能夠讀取.bash_history數據庫

在這裏又能夠讀到web的源碼地址,因此能夠繼續讀取它:json

在這裏可以發現flag所在的庫和表,因此就能夠讀取表中的內容,又由於linux下,mysql安裝後,數據庫的數據默認存放在/var/lib/mysql目錄下,因此能夠直接訪問其中的庫表,因此能夠直接讀取

/var/lib/mysql/security/flag.ibd

 3.just soso

這道題比較常規

<html>
<?php
error_reporting(0); 
$file = $_GET["file"]; 
$payload = $_GET["payload"];
if(!isset($file)){
    echo 'Missing parameter'.'<br>';
}
if(preg_match("/flag/",$file)){
    die('hack attacked!!!');
}
@include($file);
if(isset($payload)){  
    $url = parse_url($_SERVER['REQUEST_URI']);
    parse_str($url['query'],$query);
    foreach($query as $value){
        if (preg_match("/flag/",$value)) { 
            die('stop hacking!');
            exit();
        }
    }
    $payload = unserialize($payload);
}else{ 
   echo "Missing parameters"; 
} 
?>
<!--Please test index.php?file=xxx.php -->
<!--Please get the source of hint.php-->
</html>

這裏主要記錄一下繞過parse_url,這裏會檢測flag字符串,可是要是讓parse_url

這樣就能使parse_url返回false,這樣繞過對flag的過濾,而後後面就是常規的反序列化漏洞,這裏要記住最後的序列化的數據由於有不可見字符因此須要urlencode一下

 4.math

<?php 
error_reporting(0); 
//據說你很喜歡數學,不知道你是否愛它賽過愛flag 
if(!isset($_GET['c'])){ 
    show_source(__FILE__); 
}else{ 
    //例子 c=20-1 
    $content = $_GET['c']; 
    if (strlen($content) >= 80) { 
        die("太長了不會算"); 
    } 
    $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]']; 
    foreach ($blacklist as $blackitem) { 
        if (preg_match('/' . $blackitem . '/m', $content)) { 
            die("請不要輸入奇奇怪怪的字符"); 
        } 
    } 
    //經常使用數學函數http://www.w3school.com.cn/php/php_ref_math.asp 
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
    preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs); 
    foreach ($used_funcs[0] as $func) { 
        if (!in_array($func, $whitelist)) { 
            die("請不要輸入奇奇怪怪的函數"); 
        } 
    } 
    //幫你算出答案 
    eval('echo '.$content.';'); 
}

 方法一:

這道題主要仍是構造沒有字母的shell,這裏面又提供了進制轉換的函數base_convert(),說明能夠用0-9a-z 36個字符,那麼就能夠構造shell,這裏主要經過分析一個payload:

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat flag.php
這裏經過構造動態函數,首先base_convert()構造hex2bin,把16進制轉換爲字符串,再經過「_GET」 -> 16進製表示,再到10進製表示,而後反過來dechex()->hex2bin(),而後結合動態函數
好比$a="_GET";$$a{c}(($$a){d}); 這樣將實際的payload放在GET參數中,從而來減少長度。
另一個點是php的數組不只能夠經過[]來進行索引,還能夠經過{}來進行索引。
方法2:
另外一種構造出_GET的方法是經過異或字符串:
好比要獲得_G,則能夠經過:

具體怎麼得出:能夠經過「_G」和兩個字符異或:

for($j=0;$j<10;$j = $j+1){
    for($i=0;$i<10;$i = $i+1){
    echo $i.$j." ";
    echo "_G"^($j).($i);
    echo "\n";
}}

能夠獲得兩位字符串,這裏也能夠選3位或者4位跑,可是由於獲得的字符串須要在白名單裏面找,因此太長了找不到,因此選兩位最好,一位會增長payload長度,所以is是在白名單裏存在的,因此就能夠使用,一樣的方法去找「ET」,最後
仍是去構造動態函數就能夠了。
$abs=(is_finite^(6).(4)).(rad2deg^(7).(5));$$abs{acos}($$abs{ceil})
相關文章
相關標籤/搜索