Ivanti Avalanche被报告了一个任意文件上传漏洞。这个漏洞的原因是在FileStoreConfig应用中存在不恰当的输入验证。
一个远程的身份认证攻击者可以通过发送精心构造的请求利用这个漏洞。成功的利用这个漏洞可以导致系统远程代码执行。
漏洞分析Ivanti Avalanche是美国Ivanti公司的一套移动设备管理系统。该系统主要用于管理智能手机、平板电脑等设备。Avalanche的Central FileStore和中央文件服务器用于存储和分发文件,这些文件与移动设备配置用的载荷有关。例如,.apk文件或者操作系统更新文件可以存储在Central FileStore。Central FileStore与理解这个漏洞有关。
Avalanche的Web接口可以通过HTTP的8080端口访问,例如:
http://ip:8080/AvalancheWeb/login.jsf
Avalanche允许用户通过web界面更改文件存储路径来更改Central FileStore保存文件的位置。发送请求到AvalancheWeb/app/ FileStoreConfig接口,由com. wavink.amc.web .view. filestoreconfigbean类处理处理这个请求,可以实现更改文件存储路径。这个请求包括一个txtUncPath请求参数,该参数包含存储文件的新路径。在保存新路径之前,调用validateFileStoreUncPath方法来验证新路径是否被允许。根据不允许的值的拒绝列表和目录遍历字符检查路径。如果路径通过检查,则保存新路径。以后上传到FileStore的文件将存储到新的位置。
Central FileStore中存在一个任意文件上传漏洞。该漏洞是由于对Central FileStore配置设置中的txtUncPath字段处理不足造成的。validateFileStoreUncPath方法试图阻止新路径中包含Avalanche服务器的webroot文件夹。然而,validateFileStoreUncPath方法不阻止使用远程控制服务器的webroot父文件夹:“C:\ProgramData\ wavink \Avalanche\RemoteControlServer\app\”。攻击者可以将txtUncPath值设置为“C:\ProgramData\ wavink \Avalanche”,绕过路径的合法性检查。然后,攻击者可以发送请求,将恶意文件上传到“RemoteControlServer\app”子文件夹。远程控制服务器通常用于控制连接的Windows Mobile/CE设备,可以通过向http://:1900/发送HTTP请求来访问。默认情况下,远程控制服务器执行Velocity宏代码。通过将精心制作的文件上传到远程控制服务器的webroot目录,攻击者就可以在系统上执行任意命令。
源代码走查下面的代码片段取自Ivanti Avalanche 6.4.1版本。
.dialogTree .ui-dialog-content.ui-widget-content {
padding: 0;
background: none;
}
[ ...truncated for readability... ]
#{bundle.filestoreConfig_sharedmapping_unc_path_lbl}
[ ...truncated for readability... ]
下面的代码来自反编译文件WEB-INF.classes.com.wavelink.amc.web.view. CentralFileStoreDialog class in AvalancheWeb.jar
@ManagedBean(name = "fileStoreConfig")
@SessionScoped
public class FileStoreConfigBean extends AbstractViewBean implements Serializable {
@ManagedProperty("#{pageFlowScope}")
protected PageFlowScope pageFlowScope;
private FileStoreConfigPayload m_currentConfig;
private String m_unc;
private String m_uname;
private String m_upass;
private String m_cert;
private String m_certShortFileName;
private String m_certpass;
private String m_serverUrl;
private String m_velocityFolder;
private String m_velocityCertHash;
private String m_defaultunc;
private boolean m_configSaved = false;
private String m_notAfter;
public void setPageFlowScope(PageFlowScope pageFlowScope) {
this.pageFlowScope = pageFlowScope;
}
private static final Logger logger = LogManager.getLogger(
com.wavelink.amc.web.view.FileStoreConfigBean.class);
// List of disallowed paths does not include RemoteControl server webroot parent folders
private static final String[] g_exclPatterns = new String[] { ".*?/AvalancheWeb.*", ".*?/webapps.*",
".*?/Users.*", ".*?/Program Files.*", ".*?/Program Files (x86).*", ".*?/AVALAN~1.*", ".*?/PROGRA~1.*",
".*?/PROGRA~2.*", ".*?/Windows.*", ".*?/RemoteControlServer.*" };
private List m_exclusionPatterns = new ArrayList();
public String onLoad() {
logger.trace("FileStoreConfigBean.onLoad()");
init();
return "viewFileStoreConfig";
}
@PostConstruct
public void init() {
// Generate list of excluded paths
generatePatternList(g_exclPatterns, this.m_exclusionPatterns);
getCurrentFileStoreConfig();
if (this.m_currentConfig == null) {
logger.error("No current FileStore configuration");
} else {
setSavedFields();
}
}
[ ...truncated for readability... ]
public void validateFileStoreUncPath(FacesContext context, UIComponent component,
Object value) throws ValidatorException {
logger.trace("FileStoreConfigBean.validateFileStoreUncPath()");
// Attacker controlled path value
String uncPath = value.toString();
if (uncPath.isEmpty())
return;
String testPath = uncPath.replace("\\", "/");
String default63Path = this.m_default63unc.replace("\\", "/");
String default64Path = this.m_default64unc.replace("\\", "/");
String currentPath = this.m_unc.replace("\\", "/");
if (testPath.contains(".."))
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
BundleManager.getBundleString("inputError", "file_store_unc_path_parent"), null));
if (testPath.contains("./") || testPath.contains("/."))
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
BundleManager.getBundleString("inputError", "file_store_unc_path_current"), null));
if (testPath.endsWith("/"))
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
BundleManager.getBundleString("inputError", "file_store_unc_path_trailer"), null));
// Attempt to exclude paths but does not account for parent folder of webroot folder
for (Pattern patt : this.m_exclusionPatterns) {
Matcher matcher = patt.matcher(testPath);
if (matcher.find()) {
if (testPath.equalsIgnoreCase(default63Path) && testPath.equalsIgnoreCase(currentPath)) {
logger.debug(String.format("Filestore path '%s' is forbidden but tolerated because
it is the default CFS path", new Object[] { uncPath }));
continue;
}
logger.error(String.format("Filestore path '%s' is forbidden", new Object[] { uncPath }));
logger.info("ProposedPath: " + testPath);
logger.info("Default63Path: " + default63Path);
logger.info("Default64Path: " + default64Path);
logger.info("CurrentPath: " + currentPath);
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
BundleManager.getBundleString("inputError", "file_store_unc_path_forbidden"), null));
}
}
boolean uncStart = uncPath.startsWith("\\");
boolean dosStart = (uncPath.length() > 1 && uncPath.charAt(1) == ':' && Character.isLetter(uncPath.charAt(0)));
if (!uncStart && !dosStart)
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
BundleManager.getBundleString("inputError", "file_store_unc_path"), null));
if (!isPathValid(uncPath))
throw new ValidatorException(new FacesMessage(FacesMessage.SEVERITY_ERROR,
BundleManager.getBundleString("inputError", "file_store_unc_path_syntax"), null));
}
检测建议为了检测利用此漏洞的攻击,检测设备必须监控和解析TCP端口8080 (HTTP)和8443 (HTTPS)上的流量。注意,流量可能是SSL加密的。在继续执行后续步骤之前,可能需要检测设备对流量进行解密。
检测设备必须监控所有包含以下路径的URI的HTTP POST请求:
/AvalancheWeb/app/FileStoreConfig.jsf
如果匹配到这样的请求,那么检测设备必须在请求体中搜索linkFileStoreConfigSave参数。如果linkFileStoreConfigSave参数值为“linkFileStoreConfigSave”,则必须检查txtUncPath参数的值,以查找以下字符串:
ProgramData\Wavelink\Avalanche
如果存在,则应认为该请求是可疑的,因为很可能正在进行利用此漏洞的攻击。下面是一个恶意请求的例子:
POST /AvalancheWeb/app/FileStoreConfig.jsf HTTP/1.1 Accept-Encoding: identity
Content-Length: 661
Host: 172.16.20.98:8080
Cookie:
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux_x86_64; rv:94.0) Gecko/20100101 Firefox/94.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Origin: http://172.16.20.98:8080/
Referer: http://172.16.20.98:8080/AvalancheWeb/app/inventory.jsf
Content-Type: application/x-www-form-urlencoded
Faces-Request: partial/ajax
X-Requested-With: XMLHttpRequest
Connection: close
javax.faces.partial.ajax=true&
javax.faces.source=linkFileStoreConfigSave& javax.faces.partial.execute=linkFileStoreConfigSave formFileStoreConfig& javax.faces.partial.render=messages txtUncPath txtUname txtUpass txtCertificateFileName txtCertPass txtServerUrl txtVelocityFolder txtVelocityCertHash& linkFileStoreConfigSave=linkFileStoreConfigSave& formFileStoreConfig=formFileStoreConfig&
txtUncPath=C:\ProgramData\Wavelink\Avalanche&
txtUname=&
txtUpass=&
txtCertificateFileName=&
txtCertPass=&
txtServerUrl=https://172.16.20.98:9000&txtVelocityFolder=& javax.faces.ViewState=-8991943161012586899:2086980044940355964
总结Ivanti在发布6.4.2版本时修补了这个漏洞和其他几个漏洞。没有列出其他缓解措施,因此建议Ivanti Avalanche用户测试并部署此补丁以完全解决此漏洞。
特别感谢趋势科技研究团队的Lucas Miller和Dusan Stevanovic对这个漏洞进行了如此彻底的分析。
原文英文