fix: apply security patches for configuration file and local executor #97

Closed
蔡雨翔524370910013 wants to merge 1 commits from arthurcai/JOJ3:master into master
First-time contributor

Privilege Escalation through Hacking Configuration File (local executor)**

  1. A student can add files as.gitea/workflows/*.yaml as configuration. They can invoke joj3 with custom -conf-root and -conf-name arguments pointing to files within their own workspace.

  2. A student can provide a malicious conf.json using yaml executing bash commands that uses the local executor. The local executor bypasses the go-judge sandbox and runs arbitrary shell commands directly via exec.Command. Since joj3 is running as the privileged tt user, the attacker gains full code execution as tt, allowing them to steal secrets (e.g., ~/.config), tokens, or write to arbitrary locations.

  3. Even if push.yaml is locked and specifies -conf-root /home/tt/.config/joj/push, the vulnerability is exploitable via -fallback-conf-name and -conf-name. By providing a -fallback-conf-name, joj3's [GetConfPath()] will fail to find conf.json under the locked confRoot (because the student can commit an invalid scope), and it will execute the fallback logic filepath.Join(confRoot, fallbackConfName). This path manipulation escapes the locked root directory and executes the malicious JSON file sitting in the student's repository.

The above is tried and realized in this action. The above is written partly by Artificial Intelligence.

The code is used to fix the bug.

## Privilege Escalation through Hacking Configuration File (`local` executor)** 1. A student can add files as`.gitea/workflows/*.yaml` as configuration. They can invoke `joj3` with custom `-conf-root` and `-conf-name` arguments pointing to files within their own workspace. 2. A student can provide a malicious `conf.json` using yaml executing bash commands that uses the `local` executor. The `local` executor bypasses the `go-judge` sandbox and runs arbitrary shell commands directly via `exec.Command`. Since `joj3` is running as the privileged `tt` user, the attacker gains full code execution as `tt`, allowing them to steal secrets (e.g., `~/.config`), tokens, or write to arbitrary locations. 3. Even if `push.yaml` is locked and specifies `-conf-root /home/tt/.config/joj/push`, the vulnerability is exploitable via `-fallback-conf-name` and `-conf-name`. By providing a `-fallback-conf-name`, `joj3`'s [GetConfPath()] will fail to find `conf.json` under the locked `confRoot` (because the student can commit an invalid scope), and it will execute the fallback logic `filepath.Join(confRoot, fallbackConfName)`. This path manipulation escapes the locked root directory and executes the malicious JSON file sitting in the student's repository. The above is tried and realized in this [action](https://focs.ji.sjtu.edu.cn/git/ece280s2/test-repo/actions/runs/169). The above is written partly by **Artificial Intelligence**. The code is used to fix the bug.
蔡雨翔524370910013 added 1 commit 2026-03-12 21:47:50 +08:00
fix: apply security patches for configuration file and local executor
Some checks failed
build / build (pull_request) Failing after 37s
build / trigger-build-image (pull_request) Has been skipped
0a2d783dcd
蔡雨翔524370910013 requested review from 张泊明518370910136 2026-03-12 21:48:52 +08:00
蔡雨翔524370910013 changed title from WIP: fix: apply security patches for configuration file and local executor to fix: apply security patches for configuration file and local executor 2026-03-12 23:10:48 +08:00
张泊明518370910136 reviewed 2026-03-13 08:39:53 +08:00
@ -186,0 +194,4 @@
return confPath, confStat, conventionalCommit, err
}
} else {
slog.Warn("could not determine file ownership, proceeding with caution", "path", confPath)

What if we just terminate it here?

What if we just terminate it here?
Owner

maybe end + write logs to raise an alert on grafana?

maybe end + write logs to raise an alert on grafana?
张泊明518370910136 reviewed 2026-03-13 08:40:25 +08:00
@ -11,3 +15,3 @@
func init() {
stage.RegisterExecutor(name, &Local{})
if os.Getenv("JOJ3_ENABLE_LOCAL_EXECUTOR") == "true" {

Where is it used?

Where is it used?
Author
First-time contributor

Just thinking that the Local executor is unsafe if conf.go is bypassed, so you can add an env for the students' executor so that it doesn't run the local executor, while other usages can still be fulfilled.

Just thinking that the Local executor is unsafe if conf.go is bypassed, so you can add an env for the students' executor so that it doesn't run the local executor, while other usages can still be fulfilled.

What does it mean by students' executor?

What does it mean by students' executor?
Author
First-time contributor

What does it mean by students' executor?

I found that I'm wrong. Many stages seem to use the local executor.

> What does it mean by students' executor? I found that I'm wrong. Many stages seem to use the local executor.
Author
First-time contributor

This part is not needed.

This part is not needed.
张泊明518370910136 reviewed 2026-03-13 08:41:39 +08:00
@ -186,0 +188,4 @@
if stat, ok := confStat.Sys().(*syscall.Stat_t); ok {
uid := int(stat.Uid)
currentUid := os.Getuid()
if uid != 0 && uid != currentUid {

Will the conf file be owned by root?

Will the conf file be owned by root?
Owner

i was wondering the same: we have student and tt. i think no other user (including root) is involved.

i was wondering the same: we have `student` and `tt`. i think no other user (including `root`) is involved.
Author
First-time contributor

Then uid != 0 is not needed.

Then uid != 0 is not needed.
Some checks are pending
build / build (pull_request) Failing after 37s
Required
Details
build / trigger-build-image (pull_request) Has been skipped
build / build (push)
Required

Pull request closed

Sign in to join this conversation.
No description provided.