API安全所面临的风险-学习笔记

API(Application Programming Interface, 应用程序接口)是一些预先定义的接口(如函数、HTTP接口),或指 软件系统不同组成部分衔接的约定。 [1] 用来提供 应用程序与开发人员基于某 软件或硬件得以访问的一组 例程,而又无需访问源码,或理解内部 工作机制的细节。

在当今应用驱动的世界中,创新的一个基本要素是应用程序编程接口(API)。从银行、零 售、运输到物联网、自动驾驶汽车、智能城市,API都是现代移动、SaaS 和 Web应用程序的一个 关键组成部分,并且在面向客户、面向合作伙伴和面向内部的应用中都会使用到。 一般来说,API会公开应用程序的逻辑和敏感数据,如:个人识别信息(PII),正因为如此, API越来越成为攻击者的目标。

未授权访问

风险:攻击者可以在发送的请求中改变对象 的ID来攻击存在“失效的对象级授权” 漏洞的API。这将导致敏感数据的未授 权访问。这已经成为最普遍、且影响广泛的API攻击。

举例:Docker API 未授权访问 新姿势之Docker Remote API未授权访问漏洞分析和利用 - SecPulse.COM | 安全脉搏

造成原因:服务器通常不会完整地 跟踪用户的状态,而是依赖用户请求 参数中的对象ID来决定访问哪些目标对象。

防范:

  • 前端调用后端的接口,必须由后端作二次校验,不能只相信前端页面控制;
  • 系统对系统的接口,需要用身份认证机制(比如,appId和appSecret机制、token机制)
场景
一个在线电子商务平台为他们的入驻商户提供了 一个带有利润图表的列表页。通过查看浏览器发出的请求, 攻击者可以识别到为此图表提供数据源的API 端点及其模式 /shops/{shopName}/revenue_data.json。通过另一个API端点, 攻击者可以获取入驻平台的商户名列表,用一个简单的脚 本即可获取到入驻商户名,通过替换URL中的{shopName}, 攻击者获取到数千入驻商户的销售数据。

越权问题

风险:攻击者试图访问权限范围外,(水平越权或者垂直越权)的数据操作。

原因:负责认证 的API端点没有区别于普通端点对待,并且没有实现额外的保护。本质上是没有做好身份鉴定。

举例:API越权 API越权风险检测方式浅谈_黑客技术

防范:

  • 不能信任接口调用传入的参数,必须由后端对访问做严格的限制
  • 前端调用后端的接口,由后端做二次校验
  • 不相信前端传入的参数,而是后端自动获取当前登录用户的权限相关的信息;
  • 后端不能只判断用户是否登录,还应该判断用户是否有权限
  • 当传入id查询或者操作的时候,一定要检测当前用户是否有当前的权限
场景
攻击者通过向/api/system/verification-codes发起一 个POST请求并在请求体中提供用户名来启动重置密码工作 流。然后一个6位数字的SMS短信验证码发送到受害者的手 机。由于该API没有实现调用频率限制,攻击者可以用多线 程脚本向/api/system/verification-codes/{smsToken}发送请求 来尝试所有可能的验证码组合,从而在几分钟内破解验证 码。

DDOS与资源耗尽攻击

风险:无法正常服务

原因:无需身份认证,没有进行速率限制, 即可使用单一的本地计算机或云计算 资源来执行多个并发请求。

防范:

  • 采用WAF和防火墙;
  • 采用流量清洗和黑洞技术;
  • 设置IP白名单;
  • 对接口调用频率和调用次数进行限制。
场景:当应用包含一个每页可以显示200个用户的列表界 面时,我们可以使用/api/users?page=1&size=100查询从服务 器中检索用户列表。攻击者可以将size参数篡改至 200 000, 从而导致数据库出现性能问题。同时,API将无法响应该客 户端的其他请求,或其他客户端的请求,形成DoS。

重放攻击

风险:攻击者获取一段报文后,重复多次请求接口。

举例:API重放攻击 防止重放机制 - 简书

防范:

  • 在请求报文中加入随机数(nonce)和签名,如果随机数重复则服务端认为是无效请求;(该方法的不足是需要维护一张随机数的全表记录,如果用Redis来存储可能会占用较大内存)
  • 在请求报文中加入时间戳(timestamp)和签名,如果请求报文的时间戳与服务端的时间差较大(比如1分钟),则认为是无效请求;(该方法的不足是在允许的时间差内,仍然有被重放攻击的风险)
  • 结合nonce和timestamp机制(只在允许的时间差内维护随机数的全表记录,比如在Redis中随机数全表记录有效期只保留1分钟,这样就可以节约内存);
  • 一次性token机制,token使用一次后就失效。

注入攻击

风险:攻击者传入一些畸形数据,让接口执行一些意想不到的操作。

防范:

  • 程序和数据分离,不允许调用者来控制如何执行程序;
  • 使用预编译SQL,而不是动态拼接SQL;
  • 在接口入参对象中只放必要的属性,且操作时只修改必要的属性。(防止利用JSON字符串和Object自动绑定特性,来传入多余的JSON属性,来更新整个对象)。
  • 避免使用将客户输入自动绑定到代码变量或 内部对象中的函数
场景
一个视频共享门户允许用户上传并下载不同格式的内容。 研究API的攻击者发现,端点GET /api/v1/videos/{videoid}/metadata 返回具有视频属性的JSON对象。属性之一是”mp4_conversion_ Params”: ”-v codec h264”,表示应用程序使用shell命令来转换视频。 攻击者还发现端点POST /api/v1/videos/new容易受到批量分配缺陷 的影响,并允许客户端设置视频对象的任何属性。攻击者将恶意 值设置如下:”mp4_conversion_params”:”-v codec h264 && format C: /”。一旦攻击者将视频下载为MP4,此值将导致注入shell命令。一个视频共享门户允许用户上传并下载不同格式的内容。 研究API的攻击者发现,端点GET /api/v1/videos/{videoid}/metadata 返回具有视频属性的JSON对象。属性之一是”mp4_conversion_ Params”: ”-v codec h264”,表示应用程序使用shell命令来转换视频。 攻击者还发现端点POST /api/v1/videos/new容易受到批量分配缺陷 的影响,并允许客户端设置视频对象的任何属性。攻击者将恶意 值设置如下:”mp4_conversion_params”:”-v codec h264 && format C: /”。一旦攻击者将视频下载为MP4,此值将导致注入shell命令。

劫持篡改攻击

风险:攻击者获得一段报文后,篡改报文中的内容,再请求接口。

举例:https://pay.weixin.qq.com/wiki/doc/api/wxa/wxa_api.php?chapter=4_3

防范:

  • 采用API签名(sign)方式来防止数据被篡改;
  • 客户端对请求报文进行签名,服务端验签通过后,才响应请求;
  • 服务端对响应报文进行签名,客户端验签通过后,才相信响应报文;
  • 常用的签名算法包括SHA256或MD5,推荐使用SHA256(哈希算法,签名不可逆计算出原始值);
  • 签名时需要同时考虑防止签名被预测和重放攻击,需要将nonce和timestamp一起签名,保证每次签名(sign)值都不同
场景
:在仅允许受邀用户加入的应用程序注册过程中, 移动应用程序将触发对GET /api/invites/ {invite_guid}的API调 用。响应包含一个JSON,其中包含有关邀请的详细信息, 包括用户的角色和用户的电子邮件。 攻击者复制了请求,并操纵HTTP方法和端点进行POST /api/invites/new。管理员只能使用管理控制台访问该端点, 该控制台未实现功能级别的授权检查。 攻击者利用此问题并向自己发送邀请以创建管理员帐户: POST /api/invites/new {“email”:” hugo@malicious.com”,”role”:”admin”}

代码,数据,URL泄露

风险:返回或者请求中带有敏感数据

原因:没有进行脱敏和加密

防范:

  • 不要存放敏感信息,即使经过加密
  • 由后端来对敏感数据进行脱敏,不要依赖于前端进行脱敏。
  • 尽量使用HTTP POST,来在HTTP Request body中传输参数,再采用HTTPS协议对传输过程加密;
场景
一个基于物联网的视频监控系统允许管理员创建 具有不同权限的用户。当管理员为一个新的安保人员创建 账户时,应当在站点上只分配特定建筑范围的访问权限。 当安保人员使用手机应用时,API会触发调用/api/sites/111/ cameras 来接收可用摄像头传输的数据,并显示在显示屏上。 这个响应包含了一个基于{"id":"xxx","live_access_token": “xxxxbbbbb”,“building_id”:“yyy”}格式的摄像头详细列表。因 此,当客户端的图形化界面仅显示安保人员应当可以访问 的界面时,实际上API返回了站点内所有摄像头的完整列表。

API安全注意事项

  • 互不信任原则:接口提供方不信任接口调用方的请求参数,接口调用方不信任接口提供方的返回数据;
  • 不信任网络原则:假设网络传输过程是不安全的,网络中的每一个节点都是不安全的;
  • 采用HTTPS协议,保证传输过程安全;
  • 作最坏的打算,即使数据被窃听,也无法解密;
  • 设立网络安全边界,采用WAF、防火墙、网络隔离、IP白名单、流量清洗和黑洞技术来防止DDoS攻击;
  • 使用API网关,在API网关上实现安全机制和限流,简化API实现;
  • 采用包含了nonce和timestamp的API签名来防止数据被篡改和重放攻击;
  • 时刻关注水平越权问题;
  • 注意编码安全,防止代码泄漏和API URL泄漏

参考文章 :https://blog.youkuaiyun.com/nklinsirui/article/details/107818341?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522162547665916780265454204%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=162547665916780265454204&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-4-107818341.first_rank_v2_pc_rank_v29_1&utm_term=API%E5%AE%89%E5%85%A8

http://www.owasp.org.cn/owasp-project/OWASPAPITop102019.pdf#page=6&zoom=100,0,0

OWASP-api-top10

以下内容来自于与OWASP Top 10 for Web

improper-assets-management

不正确的资产配置

描述:在一个忘记密码的页面,点击忘记密码,使用Alice的邮箱进行密码的修改。进行抓包截取,在API为/api/v3/validate-code时,会有remaining_attempts次数为5,所以不允许对验证码进行暴力破解,但是在更换为/api/v1/validate-code后,则取消了ATTEMPTS_LEFT的设定,则可以进行暴力破解。这种情况,属于配置失误

@RestController@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)

public class AuthController {

private static int ATTEMPTS_LEFT = 5;

@PostMapping("/api/v3/validate-code")

public ResponseEntity <Object> validateCode(CodeValidationRequest codeValidationRequest) {

Map <String, Object> responseAttributes = new HashMap<>();

if (ATTEMPTS_LEFT == 0) {

responseAttributes.put("remaining_attempts", 0);

responseAttributes.put("message", "Maximum number of attempts exceeded");

return new ResponseEntity<>(responseAttributes, HttpStatus.UNAUTHORIZED);

}

String code = codeValidationRequest.getCode();

boolean isValidCode = CodeValidationManager.validateCode(code);

if (!isValidCode) {

ATTEMPTS_LEFT = ATTEMPTS_LEFT - 1;

responseAttributes.put("remaining_attempts", ATTEMPTS_LEFT);

responseAttributes.put("message", "The supplied token is invalid");

return new ResponseEntity<>(responseAttributes, HttpStatus.UNAUTHORIZED);

}

return new ResponseEntity<>(null, HttpStatus.OK);

}

}

@PostMapping(" /api/v1/validate-code" )

public ResponseEntity <0bject> val idateCodeLegacyCCodeVal idationRequest codeVal idationRequest) {

String code = codeValidat ionRequest . getCode() ;

boolean isValidCode = CodeVal idationManager . validateCode(code);

if (lisValidCode) {

Map<String, object>responseAttributes

new HashMapO();

responseAttributes . put( "message", "The supplied token is invalid");

return new ResponseEntityo responseAttributes , HttpStatus . UNAUTHORIZED);

}

CodeVal idationManager . setAuthSession();

return new ResponseEntityO(null, HttpStatus . OK);

}

}

excessive data exposure

@RestController

过度数据暴露

描述:通过忘记密码选项,可以获得Reset Token URL,之后便可以抓包修改email,得到response的url,最后访问该url实现密码的修改。

@RestController

public class ForgotPasswordController {

@PostMapping(" /api/ forgot-password" )

public ResponseEntity <LinkGeneratorResponse> handl ePasswordResetLink(ForgotPasswordRequest forgotPasswordRequest) {

String email = forgotPasswordRequest . getEmail();

boolean isValidEmail = EmailVal idator . val idateEmail email);

if (isValidEmail) {

LinkGeneratorResponse response = ResetLinkGenerator . generateResetLinkFor(email);

EmailNotification. sendResetLinkTo(email, response . getResetLink());

return new ResponseEntityO(response, HttpStatus . OK);

} else {

return new ResponseEntityo(null, HttpStatus . BAD REQUEST);

}

}

}

把generateResetLinkFor()方法生成的重置密码的链接暴露在了respnse中。另外,如果提供的电子邮件地址没有在系统中注册,handlePasswordResetLink()方法将以BAD REQUEST HTTP状态响应,泄露电子邮件是否在系统中注册的用户信息。

改进

@RestController

public class ForgotPasswordController {

@PostMapping(”/api/ forgot-password")

public ResponseEntity <0bject> handL ePasswordResetLink(ForgotPasswordRequest forgotPasswordRequest) {

String email - forgotPasswordRequest . getEmail();

boolean isValidEmail = EmailVal idator . val idateEmail(email);

if (isValidEmail) {

LinkGeneratorResponse response

ResetLinkGenerator . generateResetLinkFor(email);

EmailNotification . sendResetLinkTo email , response . getResetLink());

Map <String, Object> responseAttributes = new HashMapo( ;

responseAttributes . put( "message", "PLease check your e-mail inbox to continue the reset password process" );

return new ResponseEntityo(responseAttributes, HttpStatus . OK);

}

}

再次使用generateResetLinkFor()方法生成重置密码链接,并将其发送到提供的电子邮件。然而,这一次我们没有在HTTP响应中包含此数据,而是只包含应该在浏览器中呈现的消息。在HttpStatus.OK中,即使邮箱错误,也返回一样的信息。因此不可能推断出来是否存在。

broken-object-level-authorization

对象级授权缺失

描述:对抓包后,/api/users/{id}/orders中id进行修改,即可达到越权访问。

@RestController

public class OrdersController {

@GetMapping(”/api/ {userId}/orders" )

public ResponseEntity<OrdersResponse>getOrders @PathVariable("userId") String userId, OrdersRequest ordersRequest) {

Orders orders = OrdersManager . get0rdersByUserId(userId);

UserDetails userDetails = UserManager . getUserDetails(userld);

Map<String, object>responseAttributes - new HashMapo(;

responseAttributes . put( "user", userDetails);

responseAttributes . put("orders", orders);

return new ResponseEntityo(responseAttributes, HttpStatus . OK);

}

}

读取UserID方法,对其进行一个读取,把userDetails加入到Http中

改进

@RestController

public class OrdersController {

@GetMapping(”/api/orders")

public ResponseEntity<object>getOrders(OrdersRequest ordersRequest) {Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

String userId = authentication getUserIdC);

if (userId == null) {

return new ResponseEntityo (null, HttpStatus . FORBIDDEN);

}

Orders orders = OrdersManager . getOrdersByUserId(userId);

UserDetails userDetails - UserManager . getUserDetails(userId);

Map<String, object>responseAttributes = new HashMap<( ;

responseAttributes . put("user",userDetails);

responseAttributes . put("orders", orders);

return new ResponseEntity<>(responseAttributes, HttpStatus . OK);

}

}

这里,我们从安全的HTTP会话中检索userId值,而不是依赖于不安全的GET参数,

关于Authentication认证,可以参考这篇文章
https://www.cnblogs.com/shiyu404/p/6530894.html

api-broken-user-authentication

破碎的用户身份验证

描述:很简单,就是用户登录时候的验证码允许被暴力破解

@RestController

public class AuthController {

@PostMapping(" /api/validate-code")

public ResponseEntity <0bject> val idateCode CodeVal idationRequest codeVal idationRequest) {

String code = codeVal idationRequest . getCode();

boolean isVal idCode = CodeVal idationManager . val idateCode(code);

if (isValidCode) {

CodeVal idationManager . setAuthSession();

Map<String, object>responseAttributes = new HashMap <(;

responseAttributes . put("valid", isValidCode);

return new ResponseEntityo(responseAttributes, HttpStatus . OK);

}

}

这里没有设置尝试输入token 的次数,导致了存在暴力破解的可能。

改进代码

@RestControllergScope(value = WebAppl icationContext . SCOPE SESSION, proxyMode = ScopedProxyMode . TARGET CLASS)

public class AuthController {

private static int ATTEMPTS LEFT = 3;

@PostMapping(" /api/validate- code" )

public ResponseEntity <0bject> val idateCode CodeVaL idationRequest codeVal idationRequest) {Map <String, object> responseAttributes = new HashMap <(0;

if (ATTEMPTS LEFT > 0) {

String code=codeVal idationRequest . getCode();

boolean isValidCode = CodeValidationManager . val idateCode(code);

if (isValidCode) {

CodeVal idationManager . setAuthSession();

} else {

ATTEMPTS LEFT = ATTEMPTS LEFT - 1;

responseAttributes . put("valid", isVal idCode);

responseAttributes . put( "remained attempts", ATTEMPTS LEFT);

}

} else

CodeVal idationManager.resetCode();

responseAttributes . put("valid", false);

responseAttributes . put( "message", "Maximum number of attempts exceeded" ) ;

return new ResponseEntity< responseAttributes , HttpStatus . OK);

}

}

增加一个尝试输入的最大次数就好。

lack-of-resources-and-rate-limiting

资源缺乏与速率限制

描述:未对API做访问限制,导致请求过多,恶意注册过多

@PostMappingC" /api/sign-up" )

public ResponseEntity<0bject>signUpCSignUpRequest signUpRequest) {

boolean isVal idRequest = SignUpVaL idat ionManager . isVal id(signUpRequest);

if (isVal idRequest) {

UserManager . createNewUser( signUpRequest);

return new ResponseEntity<>(null, HttpStatus . OK);

} else {

RequestVal idation0bject validationErrors - SignUpVaL idationManager . getRequestErrors( signUpRequest);

Map<String, object>responseAttributes = new HashMapOO ;

responseAttributes .put(”error", true);

responseAttributes . put( "field", val idationErrors . getErrorField( ));

responseAttributes. put( "message", val idationErrors . getErrorMessage());

return new ResponseEntityO(responseAttributes, HttpStatus . CONFLICT);

}

}

优化

我们通过计算发送者指纹(IP地址、用户代理等)完成的请求数量并阻止所有过量的请求来实现速率限制机制。

有许多可以用于保护API的速率限制和节流机制的生产就绪实现,例如Bucket4j和其他。

除了请求节流之外,该方法现在显示一个通用错误消息,而不是特定于字段的错误消息。这可以防止攻击者获取关于数据库中已经存在哪些数据的信息。

@PostMapping”/api/sign-up" )

public ResponseEntity <0bject> signUp( SignUpRequest signUpRequest) {

int requestCounter - RequestQuotaCounter . countRequestFrom signUpRequest);

if (requestCounter > 2) {

responseAttributes . put("error", true);

responseAttributes . put( "message", "Requests quota exceeded" );

return new ResponseEntity<(responseAttributes ,HttpStatus . TOO MANY_ REQUESTS);

}

boolean isValidRequest = SignUpVal idationManager . isValid signUpRequest);

if (isVal idRequest) {

UserManager . createNewUser signUpRequest);

return new ResponseEntity<>(null, HttpStatus .0K);

} else {

responseAttributes . put("error", true);

responseAttributes . put( "message", "An error occurred during the sign-up process");

return new ResponseEntityO(responseAttributes, HttpStatus . CONFLICT);

}

}

broken-function-level-authorization

缺失的功能级授权

描述:尝试越级访问未授权ID的资源时,返回403 Forbidden,把请求方法GET换成DELETE,直接把所访问的URL资源删除

DELETE这一块具体查看HTTP方法
@RestController

public class UserController {

@GetMapping("/api/users/{userId}")

public ResponseEntity<Object> getUser(@PathVariable("userId") String userId) {

boolean isValidUserId = SessionManager.getCurrentUserId() == userId;

if (!isValidUserId) {

return new ResponseEntity<>(null, HttpStatus.FORBIDDEN);

}

UserData userData = UserManager.getUserData(userId);

return new ResponseEntity<>(userData, HttpStatus.OK);

}

@DeleteMapping("/api/users/{userId}")

public ResponseEntity<Object> deleteUser(@PathVariable("userId") String userId) {

String userEmail = UserManager.getUserEmail(userId);

UserManager.deleteUser(userId);

responseAttributes.put("status", "success");

responseAttributes.put("message", "User #{userEmail} has been successfully deleted");

return new ResponseEntity<>(responseAttributes, HttpStatus.OK);

}

}

在HTTP协议中,调用DELETE方法,却没有进行检查。

@RestController

public class UserController {

@GetMapping("/api/users/{userId}")

public ResponseEntity<Object> getUser(@PathVariable("userId") String userId) {

boolean isValidUserId = SessionManager.getCurrentUserId() == userId;

if (!isValidUserId) {

return new ResponseEntity<>(null, HttpStatus.FORBIDDEN);

}

UserData userData = UserManager.getUserData(userId);

return new ResponseEntity<>(userData, HttpStatus.OK);

}

@PreAuthorize("hasRole('ADMIN')")

@DeleteMapping("/api/users/{userId}")

public ResponseEntity<Object> deleteUser(@PathVariable("userId") String userId) {

String userEmail = UserManager.getUserEmail(userId);

UserManager.deleteUser(userId);

responseAttributes.put("status", "success");

responseAttributes.put("message", "User #{userEmail} has been successfully deleted");

return new ResponseEntity<>(responseAttributes, HttpStatus.OK);

}

}

改进后的方法是预先检查,

mass-assignment

质量数测定

描述:请求包中有isAdmin:false,修改为true,即可更改为admin权限。

@RestController

public class AuthController {

@PostMapping("/api/reset-password")

public ResponseEntity<Object>resetPassword(WebRequest request) {

String token = request.getHeader("Authorization");

boolean isValidToken = UserManager.validateResetToken(token);

if (isValidToken) {

Object user = request.getParameter("user");

UserManager.updateUser(user);

return new ResponseEntity<>(HttpStatus.ACCEPTED);

} else {

return new ResponseEntity<>(HttpStatus.FORBIDDEN);

}

}

}

改进

public class AuthController {

@PostMapping("/api/reset-password")

public ResponseEntity<Object>resetPassword(WebRequest request) {

String token = request.getHeader("Authorization");

boolean isValidToken = UserManager.validateResetToken(token);

if (isValidToken) {

String password = request.getParameter("password");

UserManager.updateUserPassword(password);

return new ResponseEntity<>(HttpStatus.ACCEPTED);

} else {

return new ResponseEntity<>(HttpStatus.FORBIDDEN);

}

}

}

更新的是password属性本身,而不是user对象,从而避免了一些风险

security-misconfiguration

安全配置错误

描述:了解被攻击对象2FA的配置方式。通过类似于钓鱼的方式,向对方服务器发送链接,当对方点击的时候,取消2FA机制

<html>

<body>

<h2>Review our cookie policy</h2>

<p>NOTICE: This website or it's third-party tools use cookies, which are necessary for its functioning and required to achieve the purposes illustrated in the cookie policy. If you want to learn more or withdraw your consent to all or some of the cookies, please refer to the cookie policy. You accept the use of cookies by closing or dismissing this banner by scrolling this page, by clicking a link or button or by continuing to browse otherwise.</p>

<button>Accept</button>

<button>Learn more and customize</button>

<iframe id="iframe" src="https://www.coinpay.com/user/security/2fa" frameborder="1" style="opacity: 0"></iframe>

</body>

</html>
2FA
2FA,2 Factor Authentication,双因子验证,是一种安全密码验证方式。区别于传统的密码验证,由于传统的密码验证是由一组静态信息组成,如:字符、图像、手势等,很容易被获取,相对不安全。2FA是基于时间、历史长度、实物(信用卡、SMS手机、令牌、指纹)等自然变量结合一定的加密算法组合出一组动态密码,一般每60秒刷新一次。不容易被获取和破解,相对安全。

sql-injection

sql注入

描述:找到注入点,sqlmap一把梭直接跑

@RestController

public class AuthController {

private static final String CONNECTION_URL = "jdbc:mysql://localhost:3306/sonoo";

private static final String CONNECTION_USER = "root";

private static final String CONNECTION_PASSWORD = "root";

@PostMapping("/api/login")

public ResponseEntity<Object>login(final LoginRequest loginRequest) throws SQLException {

final String username = loginRequest.getUsername();

final String password = loginRequest.getPassword();

final String ipAddress = loginRequest.getIpAddress();

final String userId = AuthManager.validateCredentials(username, password);

if (!userId) {

return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);

}

final Connection connection = DriverManager.getConnection(CONNECTION_URL, CONNECTION_USER, CONNECTION_PASSWORD);

final Statement statement = connection.createStatement();

final String sql = "SELECT * FROM ip_whitelist WHERE ipAddress = '" + ipAddress + "'";

final ResultSet resultSet = statement.executeQuery(sql);

if (resultSet.next()) {

SessionManager.setAuthSession();

final Map<String, Object> responseAttributes = new HashMap<>();

responseAttributes.put("id", userId);

responseAttributes.put("authenticated", true);

return new ResponseEntity<>(responseAttributes, HttpStatus.OK);

} else {

return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);

}

}

}

没有进行预编译,动态拼接导致容易存在sql注入。

改进

@RestController

public class AuthController {

private static final String CONNECTION_URL = "jdbc:mysql://localhost:3306/sonoo";

private static final String CONNECTION_USER = "root";

private static final String CONNECTION_PASSWORD = "root";

@PostMapping("/api/login")

public ResponseEntity<Object>login(final LoginRequest loginRequest) throws SQLException {

final String username = loginRequest.getUsername();

final String password = loginRequest.getPassword();

final String ipAddress = loginRequest.getIpAddress();

final String userId = AuthManager.validateCredentials(username, password);

if (!userId) {

return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);

}

final Connection connection = DriverManager.getConnection(CONNECTION_URL, CONNECTION_USER, CONNECTION_PASSWORD);

final Statement statement = connection.createStatement();

final String sql = "SELECT * FROM ip_whitelist WHERE ipAddress = ?";

PreparedStatement prepareStatement = db.prepareStatement(sql);

prepareStatement.setString(1, ipAddress);

final ResultSet resultSet = statement.executeQuery(sql);

if (resultSet.next()) {

SessionManager.setAuthSession();

final Map<String, Object> responseAttributes = new HashMap<>();

responseAttributes.put("id", userId);

responseAttributes.put("authenticated", true);

return new ResponseEntity<>(responseAttributes, HttpStatus.OK);

} else {

return new ResponseEntity<>(null, HttpStatus.UNAUTHORIZED);

}

}

}

使用PreparedStatement 进行了预编译

insufficient-logging-and-monitoring

日志和监控记录不足

描述:对一个网站,使用curl --header "域名"后,,调用var/www/application.log,却没有相关的日志信息。

@RestController

public class ProjectsController {

@GetMapping("/api/v4/projects")

public ResponseEntity<List<Project>> getProjects(@RequestHeader("Private-Token") String private_token) {

boolean isValidToken = TokenValidator.validateToken(private_token);

if (isValidToken) {

final List<Project> projects = ProjectManager.getProjects();

return new ResponseEntity<>(projects, HttpStatus.OK);

} else {

return new ResponseEntity<>(null, HttpStatus.FORBIDDEN);

}

}

}

但是,请注意,此API端点没有进行日志记录,因此开发人员无法跟踪与此端点相关的任何可疑活动。

改进

  1. 访问控制失败和服务器端输入验证失败都可以用足够的用户上下文记录下来
  2. 确保日志以集中式日志管理解决方案易于使用的格式生成
  3. 确保敏感操作有带有完整性控制的审计跟踪,以防止篡改或删除,例如只添加数据库表或类似的操作。
  4. 建立有效的监测和警报,以便及时发现可疑活动并作出反应
public class LoggingInterceptor implements ClientHttpRequestInterceptor {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Override

public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {

String timestamp = LocalDateTime.now();

String method = request.getMethod();

String uri = request.getURI();

String ip = request.getHeader('X-FORWARDED-FOR');

logger.debug("#{timestamp}: #{method} ${uri} #{ip}");

ClientHttpResponse response = execution.execute(request, body);

return response;

}

}

@RestController

public class ProjectsController {

@GetMapping("/api/v4/projects")

public ResponseEntity<List<Project>> getProjects(@RequestHeader("Private-Token") String private_token) {

boolean isValidToken = TokenValidator.validateToken(private_token);

if (isValidToken) {

final List<Project> projects = ProjectManager.getProjects();

return new ResponseEntity<>(projects, HttpStatus.OK);

} else {

return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);

}

}

}

xxe-injection

描述:在上传的xml文件中,定义了一个DOCTYPE声明loadthis来加载外部文档类型定义(DTD)文件。接下来定义了一个外部实体somefile。ENTITY声明是一个XML标记,用于从外部资源加载额外的数据。

<!DOCTYPE loadthis [<!ELEMENT loadthis ANY >
<!ENTITY somefile SYSTEM "file:///etc/passwd" >]>
<loadthis>&somefile;</loadthis>

最后在回显页面,加载除了/etc/passwd的东西

@RestController

public class XMLUploadController {

@PostMapping("/api/c-cda-upload")

public ResponseEntity<Object>uploadClinicalNotes(@RequestParam("file") MultipartFile file) {

File xmlFile = convertToFile(file);

if (!xmlFile) {

return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);

}

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

Document patientDataDocument = documentBuilder.parse(xmlFile);

JSONObject jsonData = XML.toJSONObject(patientDataDocument);

Map<String, Object>responseAttributes = new HashMap<>();

responseAttributes.put("data", jsonData);

return new ResponseEntity<>(responseAttributes, HttpStatus.OK);

}

private File convertToFile(MultipartFile file) throws IOException {

File convertedFile = new File(file.getOriginalFilename());

if (convertedFile.createNewFile()) {

FileOutputStream fos = new FileOutputStream(convertedFile);

fos.write(file.getBytes());

fos.close();

return convertedFile;

}

return null;

}

}

改进

@RestController

public class XMLUploadController {

@PostMapping("/api/c-cda-upload")

public ResponseEntity<Object>uploadClinicalNotes(@RequestParam("file") MultipartFile file) {

File xmlFile = convertToFile(file);

if (!xmlFile) {

return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);

}

DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

builder.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);

builder.setFeature("http://xml.org/sax/features/external-general-entities", false);

builder.setFeature("http://xml.org/sax/features/external-parameter-entities", false);

DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();

try {

Document patientDataDocument = documentBuilder.parse(xmlFile);

} catch(Exception e) {

return new ResponseEntity<>(null, HttpStatus.BAD_REQUEST);

}

JSONObject jsonData = XML.toJSONObject(patientDataDocument);

Map<String, Object>responseAttributes = new HashMap<>();

responseAttributes.put("data", jsonData);

return new ResponseEntity<>(responseAttributes, HttpStatus.OK);

}

private File convertToFile(MultipartFile file) throws IOException {

File convertedFile = new File(file.getOriginalFilename());

if (convertedFile.createNewFile()) {

FileOutputStream fos = new FileOutputStream(convertedFile);

fos.write(file.getBytes());

fos.close();

return convertedFile;

}

return null;

}

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值