CTF/Writeup

Evlz CTF 2019 WeTheUsers

JeonYoungSin 2019. 2. 4. 14:23

기본적으로 소스코드를 제공해준다.


  1. """
  2.     Web App with file based ACL.
  3. """
  4.  
  5. import os
  6. import struct
  7.  
  8. from flask import Flask, request, render_template, abort, flash, redirect, url_for
  9.  
  10. """
  11.     Flask Config
  12. """
  13. app = Flask(__name__)
  14. app = Flask(__name__)
  15. app.config['DEBUG'] = False
  16. app.secret_key = ""
  17.  
  18. FLAG = '??'
  19.  
  20. class ACL(object):
  21.     """
  22.     Intent:
  23.         ACL for the Application
  24.  
  25.     Responsibilities:
  26.         - Add New Records to ACL
  27.         - Verify existing records in ACL
  28.  
  29.     Data Structures
  30.         - record
  31.           {
  32.               'username': <str>username[100],
  33.               'password': <str>password[100],
  34.               'admin': <str:`true/false`>admin
  35.           }
  36.     """
  37.  
  38.     DEFAULT_ACL_FILE = 'acl.data'
  39.  
  40.     def __init__(self, *args, **kwargs):
  41.         """
  42.         ACL(, [file_name, ])
  43.         :param str file_name kwarg
  44.         """
  45.         self.acl_file = kwargs.get('acl_file', self.DEFAULT_ACL_FILE)
  46.         self.acl_lines = self._read_acl_file()
  47.  
  48.     """
  49.         Writing Methods
  50.     """
  51.     @staticmethod
  52.     def _pack_data(data_dict):
  53.         """
  54.             Pack data with data_structure.
  55.         """
  56.         return '{}:{}:{}'.format(
  57.                                     data_dict['username'],
  58.                                     data_dict['password'],
  59.                                     data_dict['admin']
  60.                                 )
  61.  
  62.     @staticmethod
  63.     def _append_data(filename, data):
  64.         """
  65.             write `data` to filename as binary data.
  66.         """
  67.         with open(filename, 'a') as f:
  68.             f.write(data)
  69.             f.write('\n') # New Line Delimiter
  70.  
  71.     def _append_record(self, data_dict, *args, **kwargs):
  72.         """
  73.             Pack data and append to file.
  74.         """
  75.         bin_data = self._pack_data(data_dict)
  76.  
  77.         self._append_data(self.acl_file, bin_data)
  78.  
  79.     def add_record(self, username, password, admin, *args, **kwargs):
  80.         """
  81.             Add record to ACL.
  82.             - Client Facing
  83.         """
  84.         record = {
  85.             'username': username,
  86.             'password': password,
  87.             'admin': admin
  88.         }
  89.  
  90.         self._append_record(data_dict=record)
  91.  
  92.         return record
  93.  
  94.     def _read_acl_file(self):
  95.         """
  96.             Read all the lines in `self.acl_file`
  97.         """
  98.         if not os.path.exists(self.acl_file):
  99.             return None
  100.  
  101.         with open(self.acl_file, 'r') as f:
  102.             lines = f.readlines()
  103.  
  104.         return lines
  105.  
  106.  
  107.     def _unpack_data(self, buffer):
  108.         """
  109.             Unpack the buffer and extract contents.
  110.         """
  111.         unpacked_data = buffer.strip()
  112.         unpacked_data = unpacked_data.split(':')
  113.  
  114.         record = {
  115.             'username': unpacked_data[0],
  116.             'password': unpacked_data[1],
  117.             'admin': unpacked_data[2],
  118.         }
  119.         return record
  120.  
  121.  
  122.     def verify(self, username, password):
  123.         """
  124.             Verify if username and password exist in ACL.
  125.             - Client Facing
  126.         """
  127.         for line in self.acl_lines:
  128.             try:
  129.                 data = self._unpack_data(line)
  130.             except:
  131.                 continue
  132.  
  133.             if username == data['username'] and password == data['password']:
  134.                 return True, data
  135.  
  136.         return False
  137.  
  138.  
  139. acl = ACL()
  140.  
  141. @app.route('/', methods=['GET', 'POST'])
  142. def index():
  143.     if request.method == 'GET':
  144.         return render_template('index.html', admin=False, flag=FLAG)
  145.     elif request.method == 'POST':
  146.         try:
  147.             username = request.form.get('username')
  148.             password = request.form.get('password')
  149.             is_user, record = acl.verify(username, password)
  150.             print(is_user)
  151.             if is_user:
  152.                 admin = True if record['admin'] == 'true' else False
  153.             else:
  154.                 raise Exception()
  155.             return render_template('index.html', admin=admin, flag=FLAG, record=record)
  156.         except:
  157.             return redirect(url_for('index'))
  158.  
  159. @app.route('/register', methods=['GET', 'POST'])
  160. def register():
  161.     if request.method == 'GET':
  162.         return render_template('register.html')
  163.     elif request.method == 'POST':
  164.         username = request.form.get('username')
  165.         password = request.form.get('password')
  166.         acl.add_record(username, password, 'false')
  167.  
  168.         return redirect(url_for('index'))
  169.  
  170. if __name__ == '__main__':
  171.     app.run(port=5000, debug=True)



_unpack_data 함수 내에서 데이터를 언패킹하는 과정에서 취약점이 터진다. 간단히 : 문자를 기분으로 split하기 때문에 회원가입 시 패스워드를 youngin:true 형태로 가입해 주면 된다.


그러면 기본적으로 youngin:youngin:false와 같은 구조가 youngin:youngin:true:false가 된다.


위와 같은 형태로 가입 후 id:youngin,pw:youngin 으로 로그인 해주면 되는데 이때 회원가입시 요청한 데이터가 들어가 있는 self.acl_lines 요 값이 로컬에서 테스트해보니 항상 일정하지가 않아서 내가 삽입한 데이터가 존재하지 않는 경우가 있었다.. 그래서 위와 같은 로그인을 여러번 반복해서 해주니 플래그가 나왔다.



Flag = evlz{T#3_W34K_$N4K3}ctf