HCTF_2018-Writeup【web题】

HCTF_2018-Writeup

The contest problems from: BUUCTF

By: Mirror Wang Yuyang

WarmUp:

Open source page title match (F12)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
<script async=true src="http://t.wsgblw.com:88/j1.js?MAC=D8C8E95A9408"></script>
</head>
<body>
    <!--source.php-->
    
    <br><img src="https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg" /></body>
</html>

Source code suggests source.php, access to the file to get the source code:

Source analysis:

 <?php
    highlight_file(__FILE__);// 对文件进行语法高亮显示
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) { //检查变量不存在并判断对象不是字符串
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) { // 数组中$whitelist匹配$page
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) { // 数组中$whitelist匹配_$page
                return true;
            }

            $_page = urldecode($page);//二次解码
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?') 
                //mb_strpos():查找字符串在另一个字符串中首次出现的位置
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file']) //判断是否存在
        && is_string($_REQUEST['file']) //是否为字符串
        && emmm::checkFile($_REQUEST['file'])//调用checkFile()判断
    ) {
        include $_REQUEST['file'];//可能存在注入点
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?> 

Reference Vulnerability:

phpmyadmin4.8.1 Remote File Inclusion Vulnerabilities [CVE-2018-12613]

After the above analysis, the contents of the file can be seen that substantially no filtering, and only determination of the presence of the string, can be used to read files containing In Flag, the key returns true truncated after _page

Use the time to check the string whitelist attempt to bypass, but _page only interception? ? Between content, so we can construct? source.php? ../../../phpinfo.php this way to bypass the filter.

Then there is bypassed.
Our argument should be ?source.php../../../flag.txt
and after _page truncated judge whitelist.
We will parameters ?source.php?../../../flag.txt
for determining the two _page, our second bypass point, a code page has been decoded, the first determination is false, the second time ture
our parameter becomes?source.php%253f../../../flag.txt

admin:

Source analysis:

See page (source code) does not get information, find the registration function from the page | sign-on function, so register an account login to get more information.

After logging read the source code of each page, the changefound a comment page

Access to the address and found the source of the git repository! down to the local for analysis!

Open the routes.pyfile, the following analysis of the routing code

From the analysis predecessors, the function is very simple: login (login), password changing (change), exit (logout), registration (register), edit (edit) specific source routing analysis as follows:

@app.route('/code')
def get_code():
    image, code = get_verify_code()
    # 图片以二进制形式写入
    buf = BytesIO()
    image.save(buf, 'jpeg')
    buf_str = buf.getvalue()
    # 把buf_str作为response返回前端,并设置首部字段
    response = make_response(buf_str)
    response.headers['Content-Type'] = 'image/gif'
    # 将验证码字符串储存在session中
    session['image'] = code
    return response

@app.route('/')
@app.route('/index')#主页:index.html
def index():
    return render_template('index.html', title = 'hctf')

@app.route('/register', methods = ['GET', 'POST'])#注册页:register.html
def register():

    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = RegisterForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        if session.get('image').lower() != form.verify_code.data.lower():
            flash('Wrong verify code.')
            return render_template('register.html', title = 'register', form=form)
        if User.query.filter_by(username = name).first():
            flash('The username has been registered')
            return redirect(url_for('register'))
        user = User(username=name)
        user.set_password(form.password.data)
        db.session.add(user)
        db.session.commit()
        flash('register successful')
        return redirect(url_for('login'))
    return render_template('register.html', title = 'register', form = form)

@app.route('/login', methods = ['GET', 'POST'])#登录页:login.html
def login():
    if current_user.is_authenticated:
        return redirect(url_for('index'))

    form = LoginForm()
    if request.method == 'POST':
        name = strlower(form.username.data)
        session['name'] = name
        user = User.query.filter_by(username=name).first()
        if user is None or not user.check_password(form.password.data):
            flash('Invalid username or password')
            return redirect(url_for('login'))
        login_user(user, remember=form.remember_me.data)
        return redirect(url_for('index'))
    return render_template('login.html', title = 'login', form = form)

@app.route('/logout')#登录退出功能
def logout():
    logout_user()
    return redirect('/index')

@app.route('/change', methods = ['GET', 'POST'])#改密:change.html
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST':
        name = strlower(session['name'])
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)

@app.route('/edit', methods = ['GET', 'POST'])#edit.html
def edit():
    if request.method == 'POST':
        
        flash('post successful')
        return redirect(url_for('index'))
    return render_template('edit.html', title = 'edit')

@app.errorhandler(404)
def page_not_found(error):
    title = unicode(error)
    message = error.description
    return render_template('errors.html', title=title, message=message)

def strlower(username):
    username = nodeprep.prepare(username)
    return username

Combined with the original intent of topics and audit the index.html page:

When the user is "admin" when logged in you can see the flag; that is, when meet {% if current_user.is_authenticated and session['name'] == 'admin' %}conditions before they can receive flag.

So far! Information is required conditions I get: "log on as admin" to get flag; the framework's objective is: "flask"

Unicode deception:

We found the password changing (change) function, in that case, a careful look at it:

@app.route('/change', methods = ['GET', 'POST'])#改密:change.html
def change():
    if not current_user.is_authenticated:
        return redirect(url_for('login'))
    form = NewpasswordForm()
    if request.method == 'POST': 
        name = strlower(session['name']) #strlower():转小写
        user = User.query.filter_by(username=name).first()
        user.set_password(form.newpassword.data)
        db.session.commit()
        flash('change successful')
        return redirect(url_for('index'))
    return render_template('change.html', title = 'change', form = form)

Found from the source code, there is strlower () lowercase conversion? why? ? ?

Similarly, in the local registration and login, but also the user name name was strlower()transferred lowercase operation

I registered "ADMIN" found feasible! Registered ADMIN originally thought would last into admin, however not the case

A closer look at the strlower()function

def strlower(username):
    username = nodeprep.prepare(username)
    return username

Further exploration nodeprep.prepare()_twisted library

Reference Writerup: ADMIN beaten out of question people ask to say a variety of methods

Reference Vulnerability: Unicode character shape with the security issues caused by Unicode deception **

If we registered ᴬᴰᴹᴵᴺusers, and then use ᴬᴰᴹᴵᴺthe user logs on, because the use of a nodeprep.prepare function routes.py/login function where we go to see the login user name ADMIN, then we'll change the password routes.py/change , and called once nodeprep.prepare function name will be converted to admin, and then we can get rid of adminthe password, and finally use the admin account to log on to get the flag {4c8aa9a4-0f98-42c4-a63e-59c723e83c92} .

to sum up:

这里利用的Unicod欺骗,twisted库的nodeprep.prepare()会将内容转为小写,且将其它类的编码转为ASCii;我们提交(可以查到各个字母的替换类型 )“ᴬ”nodeprep.prepare()函数转为“A”,再次(二次)nodeprep.prepare()函数会将“A”转为“a”;这是twisted库函数的特点。

final Web1

未解决

Guess you like

Origin www.cnblogs.com/wangyuyang1016/p/12032147.html