ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从奇安信面试复盘:安全开发工程师的路径遍历与代码审计实战

从奇安信面试复盘:安全开发工程师的路径遍历与代码审计实战 1. 想清楚再投简历安全开发工程师到底是干什么的2020年那会儿我陆续面了几家做安全产品的公司奇安信是其中一家。当时“安全开发工程师”这个岗位在招聘网站上的名字五花八门有的叫安全研发有的叫安全工具开发还有的干脆写“Java开发安全方向”。不少朋友看到“安全”两个字就以为要挖漏洞、打攻防结果去了才发现天天在写业务代码和想象中完全两码事。所以开篇我先说清楚安全开发工程师本质上是一个开发岗只是把开发技能用在安全领域比如写扫描器、做WAF规则引擎、搭建漏洞管理平台、开发终端安全产品的某个模块。这个定位决定了你面试时被考察的维度不只是“安不安全”还有“会不会写工程化代码”。我当时投奇安信看重的是它的产品线足够宽终端安全、Web安全、代码审计、威胁情报都有涉及进去之后能接触到的场景比一般安全公司丰富很多。面试流程从笔试到技术面再到综合面整体节奏紧凑题目形式也比较“正经”不像有的小厂上来就让你交一份渗透测试报告。如果你现在准备投这类岗位先别急着背面试题把岗位JD拆一遍搞清楚它招你进来是写哪块代码的再针对性地准备效率会高很多。从JD反推能力模型基本逃不出三块代码能力至少熟练掌握一门后端开发语言Java最常见其次是Python和Go要能写清楚集合、IO、并发这些基础。安全基础懂常见的Web漏洞原理知道怎么修不要求你精通0day挖掘但至少能看懂一份代码审计报告。工程化能力知道Git怎么用、CI/CD是怎么回事、测试怎么写得有意义、日志怎么打才规范。这三点权重在不同团队里不一样但基本上“代码能力 安全基础 工程化能力”。很多安全专业出身的人挂在代码能力上原理说得头头是道一让写个工具就露怯。反过来有些纯后端转过来的安全原理薄弱但代码功底扎实反而容易过笔试。想清楚这一点你就知道时间该往哪儿花了。2. 笔试复盘从一道路径遍历题目说起2.1 题目原题与出题意图奇安信这场笔试里有一道题非常典型题干大概是给了一段文件下载功能的代码要求指出漏洞并写出修复方案。核心代码逻辑类似这样String fileName request.getParameter(fileName); String filePath BASE_DIR / fileName; File file new File(filePath); // 下载文件看起来人畜无害实际上一眼就能看出是路径遍历Path Traversal也就是常说的目录穿越。攻击者只要把fileName参数传成../../etc/passwd路径就变成了BASE_DIR/../../etc/passwd如果BASE_DIR是/app/files/往上一跳就是/etc/passwd。如果系统对../没有做限制等于任意文件都能下载问题就很大了。出题人想考察的点其实有三个第一你是否能从“用户输入不可信”这个角度去审视代码第二你是否知道路径拼接的常见陷阱第三你是否能给出真正可落地的修复方案而不是嘴上说说“做过滤”就完事。很多人在第一点就栽了因为平时写业务代码习惯了直接拿参数拼路径从来没想过fileName本身就是一个攻击面。2.2 修复方案的层层推进修复路径遍历第一反应是过滤../、过滤..\、过滤绝对路径。这个思路没错但有坑。如果你只是简单地replace(.., )攻击者传....//过滤掉中间的..之后反而变成了../相当于帮攻击者构造了一个合法的穿越序列这就是经典的“过滤绕过”。所以黑名单过滤必须循环多次而且要考虑URL编码和Unicode编码的形式比如%2e%2e%2f解码后就是../。更稳妥的做法是白名单校验直接限制fileName必须匹配一段合法的文件名正则比如^[a-zA-Z0-9_\-\\.]$不允许出现路径分隔符和点号连续出现。但白名单也有业务上的限制如果系统确实需要支持子目录和中文文件名正则就得放宽。这种时候推荐用“规范化后再校验”的思路String basePath new File(BASE_DIR).getCanonicalPath(); String targetPath new File(BASE_DIR, fileName).getCanonicalPath(); if (!targetPath.startsWith(basePath File.separator)) { throw new SecurityException(非法路径); }这段代码的核心是先调用getCanonicalPath()把路径里的..和符号链接都解析成实际路径然后再判断目标路径是否还在基础目录之内。只要判断不是前缀就拒绝无论攻击者怎么变换..、./、双反斜杠最终都会被规范化之后暴露出来。我面试后复盘时把这题整理成了三层防御输入层正则白名单过滤特殊字符业务层用规范化的路径做前缀校验部署层Web服务以低权限用户运行文件目录只读三层都做到了这个漏洞才算堵得比较死。这也是安全开发面试的一个重要特征面试官不在乎你背了多少CVE编号他在乎的是你能不能把一个漏洞从原理到修复捋顺并且考虑到实际部署中的局限性。2.3 笔试题里的其他高频考点除了路径遍历这套笔试还覆盖了其他几个方向我挑几个典型的说说。SQL注入是必考的但考察方式不是让你写 or 11--而是给你一段代码让你找出问题并修复。常见的坑是PreparedStatement使用不规范比如有的同学知道用PreparedStatement却把表名或列名拼进SQL模板然后告诉你“参数已经预编译了没问题”。实际上PreparedStatement只对参数值生效表名、列名、排序字段这些结构性的部分没法用占位符替代必须做白名单映射。比如前端传orderByname后端先查一个白名单Map把name映射到实际列名column_name查不到就返回默认排序这才能堵住。XSS考察的侧重点在输出编码。出题人会问“为什么不能只过滤script”因为现在主流的XSS利用根本不依赖script标签img srcx onerroralert(1)就是经典payload更别提javascript:伪协议和各种编码绕过了。修复的核心是上下文感知的输出编码在HTML标签内输出的用HTML实体编码在JavaScript字符串里输出的做JavaScript转义在URL属性里输出的要做URL编码。这个原理不难但大部分非安全开发的程序员根本不会注意。CSRF和SSRF在笔试题里出现频率也很高。CSRF的关键是区分“浏览器自动携带凭证”和“用户主动授权”修复方案有同步Token、SameSite Cookie、二次校验自定义Header。SSRF则更考察经验因为修复方案要看业务场景如果功能就是让用户传入一个URL让服务端去访问那就必须做内网地址的过滤包括IP格式规范化、DNS解析后再校验、禁止跳转等。这些点每一个展开都是一篇文章笔试阶段能写出“问题-原理-修复-局限”这个闭环就已经胜出大多数人。3. 技术面重点代码审计能力怎么考察3.1 一份Java代码的审计思路技术面的时候面试官出了一道现场代码审计给了一段简化版的用户登录逻辑让我一边读一边说思路。那段代码大概长这样public User login(String username, String password) { String sql SELECT * FROM users WHERE username username AND password md5(password) ; ResultSet rs stmt.executeQuery(sql); if (rs.next()) { return new User(rs.getString(username), rs.getString(role)); } return null; }我当时的审计思路是沿着“外部输入怎么进入危险函数”这条主线走的第一步看输入源头。username和password都是用户可控的直接拼进SQL这是明显的SQL注入点。第二步看过滤和编码。有个md5(password)这个点很值得聊。当时有人以为md5之后就不存在注入了因为密码变成了32位十六进制字符串单引号都被处理掉了。这个说法对一半密码字段因为MD5的存在确实安全了但username没有任何处理注入路径仍然畅通。第三点面试官追问“如果我把MD5换成Base64呢”答案是不行Base64默认包含和/虽然不包含单引号但如果开发者在别的地方用了base64_decode后再拼接危险又回来了。所以代码审计的核心能力是跟着数据流走不放过每一个分支。再往深聊一点审计时要注意“二次注入”和“宽字节注入”这类变种。addslashes函数可以转义单引号但如果数据库连接用的是GBK字符集攻击者可以用%bf%27这种方式构造宽字节绕过转义。这种题目在真实笔试里不太会出现但在技术面的追问环节面试官会看你的知识边界到底在哪儿。你知道得越多越显得这个岗位的底层原理你是真的吃透了。3.2 从漏洞发现到修复建议技术面里我发现漏洞后面试官没有让我马上写修复代码而是问“你觉得这个登录功能改成预处理语句就够了吗”。这是一个典型的开放性问题。我当时答了三点面试官看起来比较认可密码不能明文存储至少要加盐哈希即便MD5也不能直接存。登录接口必须有防暴力破解机制比如基于用户名的失败次数锁定或者接口限流。返回给前端的User对象不能含有敏感字段比如不能把密码哈希带出去。实际上这三点也反映了安全开发工程师的重要素质你发现一个漏洞要能跳出来看整个业务链路。漏洞不只是代码层面的bug还可能是设计缺陷。修复了一个SQL注入如果登录接口本身没有限流不等于这个接口就是安全的。3.3 Python和Go的审计侧重点奇安信的团队里用Python写数据处理和扫描逻辑的不少所以面试中也会考察Python的代码审计能力。Python常见的安全问题里eval和exec是重灾区。很多后台工具为了灵活直接执行用户传入的表达式这是非常危险的。比如一个计算器功能用户传__import__(os).system(rm -rf /)如果代码里有eval(user_input)等于把系统权限交出去了。pickle反序列化也是Python审计的高频点。pickle.loads在解析恶意构造的序列化数据时会执行__reduce__方法指定的命令。这是一个非常隐蔽的攻击面因为代码往往看起来只是“读一个配置文件”实际上已经RCE了。修复方案就是永远不要对不可信数据做反序列化如果必须做换用JSON这类安全格式。Go在安全开发里用得越来越多尤其是写代理和端口扫描工具。面试中问Go的可能是并发安全问题比如多个goroutine同时读写一个map会panic。答题的关键是记住不要用普通map要么加互斥锁要么用sync.Map。这类问题不直接和安全相关但考察的是工程素养——安全工具自己都不能保证稳定运行谁敢在生产环境跑它。4. 安全工具开发从能跑到好用之间还差着什么4.1 一个扫描器是怎么被拆解的笔试和技术面都过了之后综合面的时候聊到项目经历。我当时讲了一个Web漏洞扫描器的项目面试官全程没有打断只是在关键节点上追问。这道题聊透了基本能代表安全开发工程师日常工作的全貌。一个Web扫描器的核心模块拆开来看大概有五个部分目标管理接收URL列表做去重和范围管理爬虫模块抓取页面、解析链接、提取表单和请求参数漏洞检测插件每个漏洞对应一个插件插件发请求、收响应、做判断结果存储与报表把扫描结果入库生成修复建议任务调度多目标多插件并发跑控制资源占用很多人写扫描器只写了“漏洞检测”这一层爬虫就用现成的库凑合一下结果扫一个小网站还行扫稍微复杂点的目标就漏得离谱。面试官问我的第一个问题是“你的爬虫怎么处理SPA单页应用”这直接把我问住了。SPA页面的数据都是异步加载的内容在JavaScript里执行后才生成传统的基于正则提取链接的爬虫根本抓不到。后来我改成用无头浏览器渲染页面再提取链接抓取覆盖率明显上来了但代价是速度慢了十倍。这个取舍在安全工具的研发里经常遇到没有完美的方案只有适不适合你的使用场景。4.2 误报和漏报的平衡怎么做讲到扫描器就绕不开误报和漏报。面试官说了一个很真实的体会安全工具最怕的不是漏报是满屏误报。漏报最多是没发现问题误报多了之后使用方会把整个工具的输出当成噪声真正的告警也会被忽略。这个观点我到现在都很认同。怎么降低误报核心思路是在漏洞检测插件里增加“验证”环节。比如检测SQL注入第一次请求是抛payload观察响应时间或页面差异。但如果只做这一步很多WAF的拦截页面会被当成“有注入”因为返回的页面确实和正常页面不一样了。所以要做二次验证用另一个不同的payload打过去看响应是否符合预期。再进一步可以结合响应内容做指纹匹配比如不同数据库的错误特征、相同漏洞在不同框架里的差异。说白了验证不是简单的“有差异就是漏洞”而是“差异的方向和预期完全一致才算”。漏报的处理更依赖漏洞库的更新频率和插件的覆盖面。在实际开发中我习惯把整个扫描结果分成三层确认漏洞、疑似漏洞、风险提示。确认漏洞走完整验证逻辑疑似漏洞只记录上下文信息不做最终判定风险提示则是根据URL范式和Header特征给出参考建议。这样既不会由于误报淹没真实告警也不会因为要求每一条都精确验证而漏掉大量线索。4.3 工具上线后的问题才是真问题面试官最后问了一个非常实战的问题“扫描器上线之后被扫描方出现业务故障你怎么排查”。我当时给了几个方向后来发现实际工作中要处理的事情更多。第一件事看并发扫描器同时发太多请求会把目标站的连接池打满表现就是业务方正常用户访问超时。解决方法是做速率控制不只是QPS维度还有单位时间内的新建连接数。第二件事看请求包是否合规很多WAF设备对异常UA和畸形请求有拦截策略我们自己扫描的时候如果没伪装好可能触发WAF封禁反而影响业务。第三件事看内容差异如果目标是上线的生产系统每次请求都可能产生脏数据比如测试payload不小心写进了数据库这在登录、留言这类功能里很容易发生。从“能跑”到“好用”中间隔着一个又一个这样的细节。面试官想看的不是你的理想架构图而是你有没有处理过这些脏活累活。5. 踩坑记录那些年我写安全工具犯过的错5.1 路径处理与编码问题的经典翻车我在安全开发生涯里踩过很多坑其中一个典型的坑就是路径处理“看似处理了实际没处理”。当时给一个文件压缩服务写接口用户传一个文件名服务端取这个文件打包下载。我按照前面说的规范化了路径做了前缀校验觉得稳了。结果测试发了几组特殊参数才发现文件名里包含中文和空格时URL编码解码出来会出问题。攻击者可以利用这个编码差异把校验时看到的字符串和实际读取文件的路径变得不一致。这就是一个典型的“二次解码”陷阱。框架解析了一遍URL编码代码又在业务层URLDecoder.decode了一遍两边解码行为不一致就可能导致绕过。后来我的解决方案是统一在最早的入口做一次标准化后续所有代码都基于标准化后的数据操作不允许中途对同一个参数再做解码。安全编码里有个原则叫“规范化”核心思想就是入口、出口、存储三处的数据都基于同一套编码状态混合编码状态就是攻击面。另一个路径相关的坑是Windows系统的路径分隔符。开发环境是Linux只处理了/部署到Windows服务器上发现大小写不敏感、路径分隔符是\之前写的正则完全拦不住。这个问题不算漏洞但暴露出安全开发里必须做的“平台兼容性测试”。当时我把整个代码的路径处理部分抽成了一个工具类所有路径校验都走这个类而不是散落在各个业务方法里这样即使出现问题也只需要改一处。5.2 编码语言引入的框架级风险写Java应用时遇到过一个很隐蔽的问题Spring框架对URL的规范化处理和Tomcat不完全一致。比如/..;/admin这种分号路径参数有时候前端经过一层过滤后认为它包含了路径穿越字符直接拒绝了但如果某个中间件会忽略分号后面的内容这个请求就能形成一种不一致。这种问题在“代理转发”场景下更明显Nginx做了$uri的规范化而内网应用却没有两边拿到不一致的路径之后就可能绕过访问控制。这个问题让我养成一个习惯凡是Web安全相关的功能代码都要去查一下底层容器和框架的版本行为差异。面试的时候我把这个经验讲给面试官听他能看出我是真的在线上环境踩过坑不是从安全书籍上背下来的。5.3 日志里的敏感信息泄露还有一次是日志的问题。我给一个认证服务加日志为了方便排查把所有请求参数都打出来了包括登录密码和Token。领导也没说什么直到安全审计的时候被第三方提出来说日志系统一旦被拖库所有用户凭证直接泄露风险等级比业务漏洞还高。这个教训让我很受触动。从那之后日志打印规范就严格了密码、Token、Cookie明文一律不打确需排查问题的打脱敏后的摘要。这个习惯后来也成了我在面试里反反复复强调的点一个安全工具类产品连自己的日志都藏不住敏感信息出来是砸招牌的。5.4 编码问题之外的部署兼容性再补充一个部署兼容性问题。很多人写安全工具的时候完全不考虑客户的实际环境。奇安信这类公司产品要部署到各种国产化环境里操作系统、CPU架构、Web容器都可能不一样。我一开始写的扫描器用了大量Python原生库在x86的CentOS上跑得好好的一到ARM架构的机器上依赖全部编译失败只能一个个打补丁。后来我给自己定了一个规矩凡是需要对外交付的工具尽量用跨平台的语言或者在开始动工之前就确认目标环境的架构和系统版本避免后期返工。这些坑单看都不大但叠加起来会导致一个安全工具“推不动”。面试的时候把这些真实案例讲出来比任何包装出来的项目都好使。6. 从面试角度看安全开发的技能树怎么点亮到了这个环节我聊聊怎么从日常积累走向一个能拿得出手的安全开发工程师。先说一个重要认知安全开发工程师的技术栈是“开发 安全”的交叉地带单独精通一边都不够必须两边同时够硬。如果你是从安全方向转过来的一定要补工程化的课程。什么叫工程化不只是会用Spring Boot写个CRUD而是掌握版本管理、依赖管理、配置管理、单元测试和持续集成。你写的代码是要在客户环境里长期跑的不是交个脚本就完事。举个例子你知道写一个扫描插件的时候如何控制它的内存占用吗知道怎么设定插件运行超时和失败重试吗这些问题都是在工程化实践中踩出来的不是看几节安全课程能解决的。如果你是从纯后端转过来的那安全基础要补。不一定要做到能独立挖0day但至少要能把OWASP Top 10的漏洞原理讲明白能看懂一个SQL注入点在代码里是怎么被利用的知道修复时候的几种主流方案及各自的适用场景。在这一块我强烈建议找一个开源的靶场项目实际打一遍亲手复现每个漏洞然后再去看修复代码。光看名词解释面试官一问细节就露馅。还有一个建议是多看真实的安全通告和漏洞分析文章。每次出现新的高危漏洞都会有一批安全分析文章出来里面有漏洞原理、利用条件、影响版本和修复方案。这不仅是学习机会也是训练“漏洞敏感度”的手段。面试时候被问到“你最近关注哪些安全事件”你如果能讲出几个技术细节和你的思考会明显加分。实习和项目经验也很重要。我见过不少候选人简历上写的项目经历全是课程设计一眼就看得出来。真正能打动面试官的项目是你为了解决一个实际问题去做的工具或平台哪怕它很小只要有完整的需求分析、方案设计、编码实现和测试验证就已经足够说明你的能力。当时我讲扫描器项目的时候从爬虫策略到误报验证再到部署遇到的环境问题讲了一个半小时面试官基本没打断。这背后不是背稿子是真的每一个细节都亲手做过、踩过坑。7. 复盘与给后来人的几条实在建议最后分享几条我的看法不算总结就是一些个人经验的碎片。当你准备投递安全开发工程师时先花时间研究目标公司的产品线和招聘JD再决定重点准备的方向。奇安信的笔试偏Web安全基础和代码能力题型也比较规范但不同团队会差别很大。做终端安全的团队可能考Windows API和驱动开发做数据安全的团队可能考加密算法和数据库权限模型针对性地准备和泛泛地刷题效果完全不一样。复习笔试的时候一定要动手写代码不要只看概念。我面奇安信之前把路径遍历、SQL注入、XSS这类的漏洞代码都实打实地写了好几遍一方面巩固了记忆另一方面面试时能脱口而出关键函数和修复姿势表达自然底气更足。面试过程中如果被问住了不要慌。安全开发的面试官很看重思维方式你说出“目前这个场景我还没遇到过但如果让我来设计我会优先考虑……”这样的回答比支支吾吾强得多。我回答SPA爬虫问题时当时确实卡壳了但我马上补了一句“如果重新设计我会用无头浏览器渲染后再抓取”面试官没有在这点上苛求我反而继续往下聊。另外面试通过之后也别急着松懈。真正进了团队你会发现学校里学的和实际要做的差距仍然很大。安全开发不是只写代码还要会跟产品经理沟通需求、跟测试团队扯清楚“误报”算不算bug、跟运维团队协调扫描任务的时间窗口。这些软技能才是从“工程师”走向“资深工程师”的分水岭。希望这篇经验帖能帮到正在准备安全开发岗位的你。如果你已经在这个方向上走了一段路欢迎带着你的故事来交流。
返回列表