diff --git a/ClientCommand.cpp b/ClientCommand.cpp index 12c7474e..0a558575 100644 --- a/ClientCommand.cpp +++ b/ClientCommand.cpp @@ -397,9 +397,9 @@ void CClient::UserCommand(CString& sLine) { return; } - sAbsolutePath = CDir::ChangeDir(m_pUser->GetDLPath(), sFile, CZNC::Get().GetHomePath()); + sAbsolutePath = CDir::CheckPathPrefix(sAllowedPath, sFile); - if (sAbsolutePath.Left(sAllowedPath.length()) != sAllowedPath) { + if (sAbsolutePath.empty()) { PutStatus("Illegal path."); return; } @@ -415,9 +415,9 @@ void CClient::UserCommand(CString& sLine) { return; } - sAbsolutePath = CDir::ChangeDir(m_pUser->GetDLPath(), sFile, CZNC::Get().GetHomePath()); + sAbsolutePath = CDir::CheckPathPrefix(sAllowedPath, sFile); - if (sAbsolutePath.Left(sAllowedPath.length()) != sAllowedPath) { + if (sAbsolutePath.empty()) { PutStatus("Illegal path."); return; } diff --git a/FileUtils.cpp b/FileUtils.cpp index 0dc7892a..395f7dde 100644 --- a/FileUtils.cpp +++ b/FileUtils.cpp @@ -467,6 +467,15 @@ CString CDir::ChangeDir(const CString& sPath, const CString& sAdd, const CString return (sRet.empty()) ? "/" : sRet; } +CString CDir::CheckPathPrefix(const CString& sPath, const CString& sAdd, const CString& sHomeDir) { + CString sPrefix = sPath.Replace_n("//", "/").TrimRight_n("/") + "/"; + CString sAbsolutePath = ChangeDir(sPrefix, sAdd, sHomeDir); + + if (sAbsolutePath.Left(sPrefix.length()) != sPrefix) + return ""; + return sAbsolutePath; +} + bool CDir::MakeDir(const CString& sPath, mode_t iMode) { CString sDir; VCString dirs; diff --git a/FileUtils.h b/FileUtils.h index 0e2abf77..36c1ff07 100644 --- a/FileUtils.h +++ b/FileUtils.h @@ -263,6 +263,9 @@ public: CFile::EFileAttr GetSortAttr() { return m_eSortAttr; } bool IsDescending() { return m_bDesc; } + // Check if sPath + "/" + sAdd (~/ is handled) is an absolute path which + // resides under sPath. Returns absolute path on success, else "". + static CString CheckPathPrefix(const CString& sPath, const CString& sAdd, const CString& sHomeDir = ""); static CString ChangeDir(const CString& sPath, const CString& sAdd, const CString& sHomeDir = ""); static bool MakeDir(const CString& sPath, mode_t iMode = 0700); diff --git a/HTTPSock.cpp b/HTTPSock.cpp index e4a92b32..5d00fdf6 100644 --- a/HTTPSock.cpp +++ b/HTTPSock.cpp @@ -119,9 +119,9 @@ bool CHTTPSock::PrintFile(const CString& sFileName, CString sContentType) { if (!m_sDocRoot.empty()) { sFilePath.TrimLeft("/"); - sFilePath = CDir::ChangeDir(m_sDocRoot, sFilePath, m_sDocRoot); + sFilePath = CDir::CheckPathPrefix(m_sDocRoot, sFilePath, m_sDocRoot); - if (sFilePath.Left(m_sDocRoot.size()) != m_sDocRoot) { + if (sFilePath.empty()) { PrintErrorPage(403, "Forbidden", "You don't have permission to access that file on this server."); DEBUG("THIS FILE: [" << sFilePath << "] does not live in ..."); DEBUG("DOCUMENT ROOT: [" << m_sDocRoot << "]"); diff --git a/Template.cpp b/Template.cpp index e3eecde8..cc92b2bb 100644 --- a/Template.cpp +++ b/Template.cpp @@ -106,6 +106,8 @@ CString CTemplate::ExpandFile(const CString& sFilename) { CString sFilePath(CDir::ChangeDir(sRoot, sFile)); if (CFile::Exists(sFilePath)) { + // This only works if sRoot got a trailing slash! The + // code which adds paths makes sure this is true. if (sRoot.empty() || sFilePath.Left(sRoot.length()) == sRoot) { //DEBUG("\t\tFound [" + sFilePath + "]\n"); return sFilePath; diff --git a/modules/webadmin.cpp b/modules/webadmin.cpp index 81c2e8b8..52aa5576 100644 --- a/modules/webadmin.cpp +++ b/modules/webadmin.cpp @@ -254,12 +254,11 @@ CString CWebAdminSock::GetAvailSkinsDir() { CString CWebAdminSock::GetSkinDir() { CString sAvailSkins = GetAvailSkinsDir(); CString sSkinDir = sAvailSkins + GetModule()->GetSkinName() + "/"; - CString sDir = CDir::ChangeDir("./", sSkinDir, "/"); + CString sDir = CDir::CheckPathPrefix("./", sSkinDir, "/"); - // Via ChangeDir() we check if someone tries to use e.g. a skin name + // Via CheckPrefix() we check if someone tries to use e.g. a skin name // with embed .. or such evilness. - if (sDir.Left(sAvailSkins.length()) == sAvailSkins - && CFile::IsDir(sSkinDir)) { + if (!sDir.empty() && CFile::IsDir(sSkinDir)) { return sSkinDir; }