Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions OAuth2LoginSuccessHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.softsafe.sast.platform.config.security;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.softsafe.sast.platform.config.OAuthUserExtractor;
import com.softsafe.sast.platform.dto.AuthUserReqDTO;
import com.softsafe.sast.platform.entity.UserInfo;
import com.softsafe.sast.platform.service.TokenService;
import com.softsafe.sast.platform.service.UserService;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.*;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.Map;

@Component
@RequiredArgsConstructor
public class OAuth2LoginSuccessHandler implements org.springframework.security.web.authentication.AuthenticationSuccessHandler {

private final TokenService tokenService;
private final OAuth2AuthorizedClientService authorizedClientService;
private final ObjectMapper objectMapper;
private final UserService userService;
private final OAuthUserExtractor oAuthUserExtractor;

private final long ACCESS_TOKEN_SECONDS = 60 * 30; // 30min
private final long REFRESH_TOKEN_SECONDS = 60 * 60 * 24 * 7; // 7d

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;

// application user id
AuthUserReqDTO authUserReqDTO = oAuthUserExtractor.extract(oauthToken.getPrincipal(), oauthToken);
UserInfo userInfo = userService.getUserInfoByOauthUserIdAndSourceType(
authUserReqDTO.getId(),
authUserReqDTO.getProvider()
);
if (userInfo == null){
userInfo = UserInfo.builder()
.oauthType(authUserReqDTO.getProvider())
.oauthUserId(authUserReqDTO.getId())
.oauthUserName(authUserReqDTO.getUsername())
.build();
userService.save(userInfo);
}

// optionally save third-party access token for backend usage (not returned to frontend)
OAuth2AuthorizedClient client = authorizedClientService.loadAuthorizedClient(oauthToken.getAuthorizedClientRegistrationId(), oauthToken.getName());
if (client != null && client.getAccessToken() != null) {
String externalToken = client.getAccessToken().getTokenValue();
// TODO save to secure storage if needed
}
try {
String accessJwt = tokenService.createAccessToken(userInfo.getId().toString(), ACCESS_TOKEN_SECONDS);
String refreshJwt = tokenService.createRefreshToken(userInfo.getId().toString(), REFRESH_TOKEN_SECONDS);
Cookie refreshTokenCookie = new Cookie("refreshToken", refreshJwt);
refreshTokenCookie.setHttpOnly(true);
refreshTokenCookie.setSecure(true);
refreshTokenCookie.setPath("/");
//TODO 配置文件
refreshTokenCookie.setDomain("lekker-hypertragically-jadwiga.ngrok-free.dev");
refreshTokenCookie.setMaxAge((int) REFRESH_TOKEN_SECONDS);
response.addCookie(refreshTokenCookie);

response.setContentType("application/json;charset=UTF-8");
objectMapper.writeValue(response.getWriter(), Map.of(
"accessToken", accessJwt,
"expiresIn", ACCESS_TOKEN_SECONDS,
"userId", userInfo.getId().toString()
));
} catch (Exception e) {
throw new ServletException(e);
}
}
}
49 changes: 49 additions & 0 deletions SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.softsafe.sast.platform.config.security;

import com.softsafe.sast.platform.config.RestAccessDeniedHandler;
import com.softsafe.sast.platform.config.RestAuthenticationEntryPoint;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final RestAuthenticationEntryPoint restAuthEntryPoint;
private final RestAccessDeniedHandler restAccessDeniedHandler;
private final OAuth2LoginSuccessHandler oauth2LoginSuccessHandler;

@Bean
@Order(2)
public SecurityFilterChain applicationSecurity(HttpSecurity http) throws Exception {

http.exceptionHandling(ex -> ex
.authenticationEntryPoint(restAuthEntryPoint)
.accessDeniedHandler(restAccessDeniedHandler)
)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers(
"/", "/login", "/login.html",
"/error",
"/github/webhook",
"/api/sast/**",
"/favicon.ico", "/icons/**"
).permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth2 -> oauth2
.successHandler(oauth2LoginSuccessHandler)
.failureUrl("/login?error")
)
.csrf(csrf -> csrf
.ignoringRequestMatchers("/github/webhook", "/api/sast/**")
);

return http.build();
}
}
101 changes: 101 additions & 0 deletions vulnerable.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#include <iostream>
#include <cstring>
#include <vector>
#include <string>
#include <cstdlib>
#include <ctime>

using namespace std;

// 1. 硬编码敏感信息 (CWE-798)
// SAST 会扫描变量名(如 api_key, secret)和高熵字符串
const string AWS_SECRET_KEY = "AKIAIOSFODNN7EXAMPLE";

class UserManager {
public:
// 2. 也是硬编码,且作为类成员
string db_password = "root";

// 3. SQL 注入 (CWE-89)
// 直接拼接字符串构建 SQL 查询是 C++ Web 后端常见的严重漏洞
void queryUser(string userId) {
string query = "SELECT * FROM users WHERE id = '" + userId + "'";
cout << "Executing query: " << query << endl;
}

// 4. 命令注入 (CWE-78)
// 允许外部输入直接进入 system() 函数
void pingHost(string ipAddress) {
string cmd = "ping -c 4 " + ipAddress;
system(cmd.c_str());
}
};

void legacyBufferOverflow(char* input) {
char buffer[10];

// 5. 经典的栈缓冲区溢出 (CWE-121)
// 虽然是 C++,但混用 C 风格字符串处理函数非常常见且危险
strcpy(buffer, input);
}

void memoryLeakAndRawPointers() {
// 6. 内存泄漏 (CWE-401)
// 使用了 new 但没有 delete
int* data = new int[100];
data[0] = 10;

// 抛出异常可能导致 delete 永远不执行 (异常安全问题)
if (data[0] == 10) {
// throw runtime_error("Error occurred!");
return;
}

delete[] data;
}

void iteratorInvalidation() {
vector<int> numbers = {1, 2, 3, 4, 5};

// 7. 迭代器失效 (CWE-835/Logic Error)
// 在遍历 vector 时进行 push_back 会导致底层数组重新分配,
// 从而使迭代器失效,导致未定义行为或崩溃。
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
if (*it == 3) {
numbers.push_back(6);
}
}
}

void weakRandomness() {
// 8. 弱伪随机数生成器 (CWE-338)
// srand/rand 不适合用于安全相关的随机数生成
srand(time(NULL));
int token = rand();
cout << "Security Token: " << token << endl;
}

int main(int argc, char* argv[]) {
UserManager um;

if (argc < 2) {
return 1;
}

// 模拟攻击路径
um.queryUser(argv[1]); // 传入 "' OR '1'='1" 即可注入

legacyBufferOverflow(argv[1]);

memoryLeakAndRawPointers();

iteratorInvalidation();

weakRandomness();

// 9. 被除数为零 (CWE-369)
int x = 0;
int y = 100 / x;

return 0;
}
68 changes: 68 additions & 0 deletions vulnerable_test.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

// 1. 硬编码凭证 (Hardcoded Credentials)
// SAST工具会扫描特定的变量名(如 password, key)和字符串比较
void check_admin(char *input_pass) {
if (strcmp(input_pass, "SuperSecretAdminPassword123") == 0) {
printf("Access Granted!\n");
}
}

// 2. 内存泄漏 (Memory Leak) & 空指针解引用 (Null Pointer Dereference)
void memory_issues() {
char *ptr = (char *)malloc(50);

// 错误:没有检查 malloc 是否返回 NULL 就直接使用
ptr[0] = 'A';

// 错误:函数结束前没有调用 free(ptr),导致内存泄漏
return;
}

// 3. 释放后使用 (Use After Free) & 双重释放 (Double Free)
void heap_corruption() {
char *data = (char *)malloc(10);
free(data);

// 错误:内存已被释放,但再次写入数据
strcpy(data, "Hello");

// 错误:再次释放同一块内存
free(data);
}

void risky_processing(char *user_input) {
char buffer[10];

// 4. 栈缓冲区溢出 (Stack Buffer Overflow)
// 如果 user_input 长度超过 10,会覆盖栈内存
strcpy(buffer, user_input);

// 5. 格式化字符串漏洞 (Format String Vulnerability)
// 如果 user_input 包含 "%s" 或 "%n" 等格式符,会导致崩溃或内存泄露
printf(user_input);
}

int main(int argc, char *argv[]) {
char local_buf[100];

// 6. 使用了被废弃且极其危险的函数 (Use of Deprecated/Unsafe Function)
// gets() 不检查缓冲区长度,是导致缓冲区溢出的头号杀手
printf("Enter specific text: ");
gets(local_buf);

if (argc > 1) {
risky_processing(argv[1]);
check_admin(argv[1]);
}

memory_issues();
heap_corruption();

// 7. 逻辑错误/死代码 (Unreachable Code)
return 0;

printf("This line will never run.");
}