Skip to content

Commit e5cd305

Browse files
committed
fix: 修复 PR langhuihui#379 中的安全问题和逻辑缺陷
1. 安全修复: - 在 MP4/FLV/HLS 插件中添加文件名安全验证 - 使用 filepath.Base() 清理路径分隔符,防止路径遍历攻击 - 验证文件名不为空且不是特殊路径(. 或 ..) 2. 逻辑修复: - 修复 MP4 API 中的录制任务查找逻辑 - 正确处理空文件名场景,避免重复录制任务 3. 代码质量: - 格式化 HLS 插件代码(修复空格/制表符混用问题) - 统一数据库模型字段命名(Filename -> FileName) 4. 配置解析修复: - 修复配置解析时的类型转换问题 - 使用 unmarshal 函数正确处理类型转换
1 parent 1af1c06 commit e5cd305

6 files changed

Lines changed: 62 additions & 26 deletions

File tree

pkg/config/config.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,11 @@ func (config *Config) ParseUserFile(conf map[string]any) {
376376
prop.ParseUserFile(vv)
377377
default:
378378
// If the value is not a map (single non-struct value), assign it to the first field
379-
prop.props[0].Ptr.Set(reflect.ValueOf(v))
379+
// Use unmarshal to handle type conversion properly
380+
fv := unmarshal(prop.props[0].Ptr.Type(), v)
381+
if fv.IsValid() {
382+
prop.props[0].Ptr.Set(fv)
383+
}
380384
}
381385
}
382386
} else {

plugin/flv/pkg/record.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,19 @@ type Recorder struct {
150150

151151
var CustomFileName = func(job *m7s.RecordJob) string {
152152
if fn := job.RecConf.FileName; fn != "" {
153+
// 安全验证:清理文件名,移除路径分隔符,防止路径遍历攻击
154+
fn = filepath.Base(fn)
155+
// 验证文件名不为空且不是特殊路径
156+
if fn == "" || fn == "." || fn == ".." {
157+
// 回退到默认命名
158+
goto defaultNaming
159+
}
153160
if !strings.HasSuffix(strings.ToLower(fn), ".flv") {
154161
fn = fn + ".flv"
155162
}
156163
return filepath.Join(job.RecConf.FilePath, fn)
157164
}
165+
defaultNaming:
158166
if job.RecConf.Fragment == 0 || job.RecConf.Append {
159167
return fmt.Sprintf("%s.flv", job.RecConf.FilePath)
160168
}

plugin/hls/pkg/record.go

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package hls
22

33
import (
4-
"context"
5-
"fmt"
6-
"strings"
7-
"path/filepath"
8-
"time"
4+
"context"
5+
"fmt"
6+
"path/filepath"
7+
"strings"
8+
"time"
99

1010
"m7s.live/v5"
1111
"m7s.live/v5/pkg/codec"
@@ -29,26 +29,34 @@ type Recorder struct {
2929
}
3030

3131
var CustomFileName = func(job *m7s.RecordJob) string {
32-
if fn := job.RecConf.FileName; fn != "" {
33-
if !strings.HasSuffix(strings.ToLower(fn), ".ts") {
34-
fn = fn + ".ts"
35-
}
36-
return filepath.Join(job.RecConf.FilePath, fn)
37-
}
38-
if job.RecConf.Fragment == 0 || job.RecConf.Append {
39-
return fmt.Sprintf("%s/%s.ts", job.RecConf.FilePath, time.Now().Format("20060102150405"))
40-
}
41-
return filepath.Join(job.RecConf.FilePath, time.Now().Format("20060102150405")+".ts")
32+
if fn := job.RecConf.FileName; fn != "" {
33+
// 安全验证:清理文件名,移除路径分隔符,防止路径遍历攻击
34+
fn = filepath.Base(fn)
35+
// 验证文件名不为空且不是特殊路径
36+
if fn == "" || fn == "." || fn == ".." {
37+
// 回退到默认命名
38+
goto defaultNaming
39+
}
40+
if !strings.HasSuffix(strings.ToLower(fn), ".ts") {
41+
fn = fn + ".ts"
42+
}
43+
return filepath.Join(job.RecConf.FilePath, fn)
44+
}
45+
defaultNaming:
46+
if job.RecConf.Fragment == 0 || job.RecConf.Append {
47+
return fmt.Sprintf("%s/%s.ts", job.RecConf.FilePath, time.Now().Format("20060102150405"))
48+
}
49+
return filepath.Join(job.RecConf.FilePath, time.Now().Format("20060102150405")+".ts")
4250
}
4351

4452
func (r *Recorder) createStream(start time.Time) (err error) {
45-
r.RecordJob.RecConf.Type = "ts"
46-
err = r.CreateStream(start, CustomFileName)
47-
if err != nil {
48-
return err
49-
}
50-
r.Debug("ts create file", "filePath", r.Event.FilePath)
51-
return nil
53+
r.RecordJob.RecConf.Type = "ts"
54+
err = r.CreateStream(start, CustomFileName)
55+
if err != nil {
56+
return err
57+
}
58+
r.Debug("ts create file", "filePath", r.Event.FilePath)
59+
return nil
5260
}
5361

5462
func (r *Recorder) writeTailer(end time.Time) {

plugin/mp4/api.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,15 @@ func (p *MP4Plugin) StartRecord(ctx context.Context, req *mp4pb.ReqStartRecord)
572572
p.Debug("mp4 plugin start record", "streamPath", req.StreamPath, "filePath", filePath, "fileName", fileName, "fragment", fragment)
573573
res = &mp4pb.ResponseStartRecord{}
574574
_, recordExists = p.Server.Records.Find(func(job *m7s.RecordJob) bool {
575-
return job.StreamPath == req.StreamPath && job.RecConf.FilePath == req.FilePath && job.RecConf.FileName == req.FileName
575+
if job.StreamPath != req.StreamPath {
576+
return false
577+
}
578+
// 如果两者都指定了文件名,需要完全匹配
579+
if req.FileName != "" && job.RecConf.FileName != "" {
580+
return job.RecConf.FilePath == req.FilePath && job.RecConf.FileName == req.FileName
581+
}
582+
// 如果只有路径匹配,也认为是同一个录制任务
583+
return job.RecConf.FilePath == req.FilePath
576584
})
577585
if recordExists {
578586
err = pkg.ErrRecordExists

plugin/mp4/pkg/record.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,20 @@ func (r *Recorder) writeTailer(end time.Time) {
148148
var CustomFileName = func(job *m7s.RecordJob) string {
149149
// 如果指定了文件名,使用指定的文件名
150150
if fn := job.RecConf.FileName; fn != "" {
151+
// 安全验证:清理文件名,移除路径分隔符,防止路径遍历攻击
152+
fn = filepath.Base(fn)
153+
// 验证文件名不为空且不是特殊路径
154+
if fn == "" || fn == "." || fn == ".." {
155+
// 回退到默认命名
156+
goto defaultNaming
157+
}
151158
// 确保文件名包含 .mp4 扩展名
152159
if !strings.HasSuffix(strings.ToLower(fn), ".mp4") {
153160
fn = fn + ".mp4"
154161
}
155162
return filepath.Join(job.RecConf.FilePath, fn)
156163
}
164+
defaultNaming:
157165
// 否则使用时间戳生成文件名
158166
now := time.Now()
159167
return filepath.Join(job.RecConf.FilePath, fmt.Sprintf("%s_%09d.mp4", time.Now().Local().Format("2006-01-02-15-04-05"), now.Nanosecond()))

recoder.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ type (
4545
StartTime time.Time `gorm:"default:NULL"`
4646
EndTime time.Time `gorm:"default:NULL"`
4747
Duration uint32 `gorm:"comment:录像时长;default:0"`
48-
Filename string `json:"fileName" desc:"文件名" gorm:"type:varchar(255);comment:文件名"`
48+
FileName string `json:"fileName" desc:"文件名" gorm:"type:varchar(255);comment:文件名"`
4949
Type string `json:"type" desc:"录像文件类型" gorm:"type:varchar(255);comment:录像文件类型,flv,mp4,raw,fmp4,hls"`
5050
FilePath string
5151
StreamPath string
@@ -89,7 +89,7 @@ func (r *DefaultRecorder) CreateStream(start time.Time, customFileName func(*Rec
8989
StartTime: start,
9090
StreamPath: sub.StreamPath,
9191
FilePath: filePath,
92-
Filename: fileName,
92+
FileName: fileName,
9393
Type: recordJob.RecConf.Type,
9494
StorageLevel: 1, // 默认为主存储
9595
StorageType: storageType,

0 commit comments

Comments
 (0)