buildat/extensions/sandbox_test/try_exploit.lua

296 lines
7.8 KiB
Lua
Raw Normal View History

-- Buildat: extension/sandbox_test/try_exploit.lua
-- http://www.apache.org/licenses/LICENSE-2.0
-- Copyright 2014 Perttu Ahola <celeron55@gmail.com>
local log = buildat.Logger("try_exploit.lua")
local magic = require("buildat/extension/urho3d").safe
local uistack = require("buildat/extension/uistack")
local dump = buildat.dump
local M = {}
-- Have a list of things to search for
local bad_names = {
-- Maybe someone is dumb enough to put these with their real names in a table?
"getmetatable",
"setmetatable",
"getfenv",
"setfenv",
-- Used in metatables
"__index",
"__newindex",
"__eq",
"__lt",
"__add",
"__sub",
"__mul",
"__div",
"__gc",
"__call",
-- Tolua++ or something metatable content
".get",
".call",
".set",
"tolua_ubox",
-- Used by buildat itself in metatables
"unsafe",
-- Dunno, but sounds unsafe
"internal",
}
local bad_values = {
-- Sandbox shouldn't be able to iterate through this
__buildat_sandbox_environment,
-- If these are leaked, sandbox is doomed
getmetatable,
setmetatable,
getfenv,
setfenv,
io,
io.open,
io.popen,
}
-- Have a list of field names to check in case of metatables
local interesting_field_names = {
"meta",
"class_meta",
}
for _, v in ipairs(bad_names) do -- All bad names are interesting
table.insert(interesting_field_names, v)
end
local bad_names_set = {}
for _, v in ipairs(bad_names) do bad_names_set[v] = true end
local bad_values_set = {}
for _, v in ipairs(bad_values) do bad_values_set[v] = true end
local interesting_field_names_set = {}
for _, v in ipairs(interesting_field_names) do interesting_field_names_set[v] = true end
local function path_str(path)
local result = ""
for _, v in ipairs(path) do
if result ~= "" then result = result.."." end
if type(v) == 'string' then
result = result..v
else
result = result..dump(v)
end
end
return result
end
local function search(value, checked_values_set, result_list, current_path)
if value == nil then
return
end
if checked_values_set[value] then
return
end
log:verbose("search: "..path_str(current_path))
checked_values_set[value] = true
if #current_path >= 20 then
table.insert(result_list, "Path too long: "..
path_str(current_path))
return
end
-- The root is never bad (it is the environment itself, which would be bad
-- as a value in any other position)
if #current_path ~= 1 then
-- Check if this value is bad
if bad_values_set[value] then
table.insert(result_list, "Bad value found: "..
path_str(current_path))
end
end
-- Try static list of field names first
for _, field_name in ipairs(interesting_field_names) do
function f()
local v = value[field_name]
if v ~= nil then
table.insert(current_path, field_name)
if bad_names_set[field_name] then
table.insert(result_list, "Bad name found: "..
path_str(current_path))
end
search(v, checked_values_set, result_list, current_path)
table.remove(current_path)
end
end
pcall(f)
end
-- Iterate through the value
if type(value) == 'table' then
-- Use meta.__next
for field_name, v in pairs(value) do
if v ~= nil then
table.insert(current_path, field_name)
if bad_names_set[field_name] then
table.insert(result_list, "Bad name found: "..
path_str(current_path))
end
search(v, checked_values_set, result_list, current_path)
table.remove(current_path)
end
end
-- Don't use meta.__next
local rawpairs = function(t)
return next, t, nil
end
for field_name, v in rawpairs(value) do
if v ~= nil then
table.insert(current_path, field_name)
if bad_names_set[field_name] then
table.insert(result_list, "Bad name found: "..
path_str(current_path))
end
search(v, checked_values_set, result_list, current_path)
table.remove(current_path)
end
end
end
end
function M.run()
-- Prepare result tables
local checked_values_set = {}
local result_list = {}
--
-- Search recursively through the base sandbox environment
-- Note that the sandbox actually can't even index its own environment
--
local start_path = {"__buildat_sandbox_environment"}
search(__buildat_sandbox_environment,
checked_values_set, result_list, start_path)
--
-- Search past global sandbox wrapper environments so we can see what kind
-- of stuff the server environment has pulled in
--
local start_path = {"__buildat_old_sandbox_global_wrappers"}
search(__buildat_old_sandbox_global_wrappers,
checked_values_set, result_list, start_path)
--
-- Create a sandbox, run a function in it which grabs its environment, and
-- run a search through it
--
local wrapper_number_was = __buildat_latest_sandbox_global_wrapper_number
local local_getfenv = getfenv
local local_pcall = pcall
local new_sandbox = {}
function f()
-- Require all the things
-- TODO: Get this list automatically from the filesystem
local extnames = {
"cereal",
"experimental",
"magic_sandbox",
"__menu",
"sandbox_test",
"test",
"uistack",
"urho3d"
}
local loaded_extensions = {}
local function try_require_extension(n)
local function g()
loaded_extensions[n] = require("buildat/extension/"..n)
end
-- There can be errors for example when the extension doesn't return
-- a safe interface
pcall(g)
end
for _, extname in ipairs(extnames) do
try_require_extension(extname)
end
-- Make this global so it stays in the environment for checking through
sandbox.make_global({loaded_extensions = loaded_extensions})
-- Get the environment (which isn't available for iteration normally)
new_sandbox.env = local_getfenv(1)
end
__buildat_run_function_in_sandbox(f)
-- Check that one wrapper was made, and include it in the search
local wrapper_number_is = __buildat_latest_sandbox_global_wrapper_number
assert(wrapper_number_is == wrapper_number_was + 1)
assert(__buildat_latest_sandbox_global_wrapper)
new_sandbox.wrapper = __buildat_latest_sandbox_global_wrapper
-- Search
local start_path = {"new_sandbox"}
search(new_sandbox, checked_values_set, result_list, start_path)
--
-- Handle results
--
local num_checked_values = 0
for k, v in pairs(checked_values_set) do
num_checked_values = num_checked_values + 1
end
for _, result in ipairs(result_list) do
log:error("Result: "..result)
end
local message = ""
if #result_list == 0 then
message = message.."No exploit found."
else
message = message..#result_list.." exploits found."
end
message = message.." Checked "..num_checked_values.." values."
M.show_result(message)
end
function M.show_result(message)
log:info("Result: "..message)
local root = uistack.main:push({desc="show_result"})
local style = magic.cache:GetResource("XMLFile", "__menu/res/main_style.xml")
root.defaultStyle = style
local window = root:CreateChild("Window")
window:SetStyleAuto()
window:SetName("show_result window")
window:SetLayout(LM_VERTICAL, 10, magic.IntRect(10, 10, 10, 10))
window:SetAlignment(HA_LEFT, VA_CENTER)
local message_text = window:CreateChild("Text")
message_text:SetName("message_text")
message_text:SetStyleAuto()
message_text.text = message
message_text:SetTextAlignment(HA_CENTER)
local ok_button = window:CreateChild("Button")
ok_button:SetStyleAuto()
ok_button:SetName("Button")
ok_button:SetLayout(LM_VERTICAL, 10, magic.IntRect(0, 0, 0, 0))
ok_button.minHeight = 20
local ok_button_text = ok_button:CreateChild("Text")
ok_button_text:SetName("ButtonText")
ok_button_text:SetStyleAuto()
ok_button_text.text = "Ok"
ok_button_text:SetTextAlignment(HA_CENTER)
ok_button:SetFocus(true)
magic.SubscribeToEvent(ok_button, "Released",
function(self, event_type, event_data)
log:info("ok_button: Released")
uistack.main:pop(root)
end)
root:SubscribeToStackEvent("KeyDown", function(event_type, event_data)
local key = event_data:GetInt("Key")
if key == KEY_ESC then
log:info("KEY_ESC pressed at show_result level")
uistack.main:pop(root)
end
end)
end
return M
-- vim: set noet ts=4 sw=4: