在開發領域,我們經常會遇到需要動態加載和執行代碼的場景。對于Python、JavaScript、Lua等腳本語言,動態性是它們的天性,而對于需要預先編譯的語言,如C#,動態執行似乎并不那么直觀。但隨著AI的普及,例如我們想在C#程序中動態執行AI生成的代碼段,這就要求我們能在運行時編譯和執行C#代碼。接下來,讓我為你介紹一個強大的框架——Natasha。
Natasha是一個基于Roslyn的C#動態程序集構建庫。它允許開發者在程序運行時動態地構建域、程序集、類、結構體、枚舉、接口和方法等。這意味著開發者可以在不停止應用程序的情況下,為其增加新的程序集。
Natasha框架具備域管理和插件管理功能,支持域的隔離和卸載,實現了熱插拔。它提供了完善的錯誤提示,自動添加引用,并且擁有完整的數據結構構建模板,從而讓開發者專注于程序集腳本的編寫。更何況它還有著跨平臺的優勢,并且對netcoreapp2.0+ / netcoreapp3.0+都兼容。
你可能會好奇,這樣一個動態編譯庫是如何彰顯其價值的?其實,動態編譯技術是支撐如今.NET生態不可或缺的重要部分。無論是在官方還是非官方的庫中,動態編譯的技術都扮演著“服務”的角色。其核心是MSIL,官方為我們提供了Emit技術來編寫IL代碼。但Emit的編寫和維護并不友好,給開發者帶來了諸多挑戰。
Roslyn的出現仿佛開啟了新世界的大門,它使得Emit變得透明,并允許我們直接用C#進行動態編譯。Natasha就是在這樣的基礎上發展起來的,經過精心設計與不斷迭代,它正成為動態編譯領域的佼佼者。
借助Natasha,你可以實現眾多有趣而實用的功能,如創建AOP代理類或動態構建Controller來實現動態API,甚至在程序啟動時與CodeFirst兼容的ORM一起使用,動態創建表結構,甚至通過動態執行AI創建的代碼段,這是個非常有趣的事情!
不可否認,這些功能的實現需要一定的編程基礎。例如,下面的代碼展示了如何使用Natasha快速開始一個域,并利用其插件管理功能。
// 開始創建域
var domain=new NatashaDomain();
// 創建非主域
var domain=new NatashaDomain(key);
// 加載插件
var assembly=domain.LoadPlugin(pluginPath);
// 使用程序集比較器
domain.SetAssemblyLoadBehavior(AssemblyCompareInfomation.UseHighVersion);
// 封裝API
domain.LoadPluginWithHighDependency(PluginPath);
在智能編譯模式下,你可以使用如下代碼快速地進行編譯,Natasha將智能地合并元數據和Using聲明,并進行語義檢查。
AssemblyCSharpBuilder builder=new AssemblyCSharpBuilder();
var myAssembly=builder.UseRandomDomain()
.UseSmartMode()
.Add("public class A{ }")
.GetAssembly();
對于更加輕便的編譯需求,Natasha提供了簡潔編譯模式。該模式會合并當前域的元數據和Using聲明,并關閉語義檢查,提供一種更加靈活快速的編譯方式。
AssemblyCSharpBuilder builder=new AssemblyCSharpBuilder();
var myAssembly=builder.UseRandomDomain()
.UseSimpleMode()
.Add("public class A{ }")
.GetAssembly();
Natasha也提供了完整覆蓋和部分覆蓋引用和using代碼的能力。例如,合并共享域的引用和using代碼可以使用以下方法:
builder.WithCombineReferences(item=> item.UseAllReferences());
builder.WithCombineUsingCode(UsingLoadBehavior.WithAll);
如果希望合并當前域的引用和using代碼或者使用自定義的引用,可以使用如下方法:
builder.WithCurrentReferences();
builder.WithCombineUsingCode(UsingLoadBehavior.WithCurrent);
// 使用自定義的元數據引用
builder.WithSpecifiedReferences(someMetadataReferences);
對于編寫和加載腳本,Natasha采用靈活的配置API來覆蓋using代碼,并添加編譯選項。這允許開發者指定腳本中要使用的C#語言版本,以及如何處理using指令。
// 配置語言版本
builder.ConfigSyntaxOptions(opt=> opt.WithLanguageVersion(LanguageVersion.CSharp6));
// 添加腳本并覆蓋Using Code
builder.WithCombineUsingCode(UsingLoadBehavior.WithAll).Add(myCode);
// 自定義覆蓋Using Code
builder.Add("script", UsingLoadBehavior.WithCurrent);
Natasha提供了一系列的With、Set和Config系列API來精細控制編譯過程。你可以配置編譯選項、診斷信息級別,甚至啟用或關閉某些特殊的編譯行為。例如,啟用語義檢查或添加語義處理插件:
// 啟用語義檢查
builder.WithSemanticCheck();
//增加語義處理插件
builder.AddSemanticAnalysistor();
動態調試
使用Natasha進行動態源代碼調試是輕而易舉的。開啟調試模式可以幫助你更深入地了解代碼執行情況,Natasha提供了多種選項來寫入調試信息:
builder.WithDebugCompile(item=> item.WriteToFile()); // 調試信息寫入文件
builder.WithDebugCompile(item=> item.WriteToAssembly()); // 調試信息整合到程序集
builder.WithReleaseCompile(); // 設置為Release模式
生成程序集
在程序集被編譯前,你可以使用Natasha提供的API來進行各種配置,比如設置程序集名稱或輸出選項:
builder.SetAssemblyName("MyAssembly");
builder.WithSemanticCheck(); // 啟用語義檢查
builder.WithFileOutput("path/to/dll", "path/to/pdb", "path/to/xml"); // 文件輸出配置
Natasha還提供了一個Codecov擴展,可幫助你獲取代碼覆蓋率數據。首先你需要引入DotNetCore.Natasha.CSharp.Extension.Codecov
擴展包,然后像下面這樣使用:
builder.WithCodecov();
Assembly asm=builder.GetAssembly();
List<(string MethodName, bool[] Usage)>? coverageData=asm.GetCodecovCollection();
Show(coverageData);
上面的Show
方法將遍歷并顯示每個方法的執行情況。這是一種很好的方式來監測你的代碼如何執行,確保質量和可靠性。
最后,Natasha提供了類型擴展來幫助你更輕松地處理類型信息。例如,獲取運行時或開發時的類型名稱,或者檢查類型是否實現了某個接口:
typeof(Dictionary<string,List<int>>[]).GetRuntimeName(); // 獲取運行時類型名稱
typeof(Dictionary<string,List<int>>).IsImplementFrom<IDictionary>(); // 檢查是否實現指定接口
當然這個項目也是開源的,不論是學習思路還是代碼設計方案 ,查看下面的項目地址都是不錯的選擇
https://github.com/dotnetcore/Natasha
后面我會使用Natasha嘗試通過AI來生成c#代碼并動態執行,可以關注我,并持續關注我的下一步行動!
Lapis是一個為Lua語言設計的Web應用開發框架,它主要針對OpenResty,這是一個基于Nginx的高性能Web平臺。Lapis不僅提供了一個簡潔而強大的API來構建Web服務,還支持現代Web開發中的多種需求,包括路由、模板、數據庫集成、安全性等。
Lapis利用OpenResty的強大性能,通過LuaJIT在Nginx內部運行Lua代碼,實現了高性能的處理能力。這意味著開發者可以享受到接近C語言級別的執行效率,同時保持Lua語言的簡潔性和靈活性。
Lapis支持Lua協程,允許開發者編寫看起來是同步的代碼,但實際上是異步執行的。這種方式可以顯著提高應用程序的并發處理能力,同時避免了回調地獄,使代碼更加清晰易讀。
Lapis提供了一個靈活的路由系統,允許開發者定義各種URL模式,并將其映射到相應的處理函數。這使得URL的設計和處理變得簡單而直觀。
Lapis內置了HTML模板系統,支持etlua模板語言,允許開發者以一種聲明式的方式編寫HTML頁面。此外,Lapis的模板系統還提供了HTML構建器語法,使得HTML的生成既安全又便捷。
Lapis支持PostgreSQL、MySQL和SQLite等多種數據庫,提供了一個強大的模型層抽象,使得數據庫操作變得簡單。開發者可以通過繼承Model類來創建自己的數據庫模型,并輕松地進行數據的增刪改查操作。
Lapis提供了CSRF保護和會話支持,幫助開發者構建更安全的Web應用。通過內置的安全特性,可以有效地防止跨站請求偽造等常見的Web安全威脅。
local lapis=require "lapis"
local app=lapis.Application()
app:match("/", function(self)
return "Hello world!"
end)
return app
app:match("/profile/:username", function(self)
local username=self.params.username
return "Welcome, " .. username .. "!"
end)
local lapis=require "lapis"
local app=lapis.Application()
class extends lapis.Application
"/":=>
"Hello world!"
["/profile/:username"]:=>
local username=@params.username
"Welcome, " .. username .. "!"
return app
local Model=require("lapis.db.model").Model
class Users extends Model
local app=lapis.Application()
app:get("/users", function(self)
local users=Users:select("*")
return { render=true, users=users }
end)
return app
local lapis=require "lapis"
local app=lapis.Application()
app:match("/", function(self)
return self:render("index")
end)
return app
Lapis是一個功能強大且高效的Web開發框架,它結合了Lua語言的靈活性和OpenResty的性能優勢。無論是構建簡單的Web服務還是復雜的Web應用,Lapis都是一個值得考慮的選擇。隨著社區的不斷壯大和生態系統的完善,Lapis有望成為Lua Web開發領域的重要力量。
FBI-Analyzer是一個靈活的日志分析系統,基于golang和lua,插件風格類似ngx-lua。
使用者只需要編寫簡單的lua邏輯就可以實現golang能實現的所有需求,點擊跳轉實現原理。
現實中可作為WAF的輔助系統進行安全分析,點擊跳轉實例。
可快速遷移waf中行為分析插件(非實時攔截需求,需要緩存計算數據的邏輯)至本系統,避免插件在處理請求時發起過多對數據緩存(redis等)的請求而導致WAF性能下降,幫助waf減負。
實現這個項目的目的其實也是加深下對lua虛擬機的認識
以及其他語言通過插件的方式調用lua腳本的工作原理,本項目因為只是單純的lua虛擬機,不是luaJIT,所以不能使用ffi也不能引用三方so的方法。
當然使用lua插件化的性能最佳的語言肯定是C,但是因為太菜了
所以只能以golang來實現,但是就目前觀察看下來,處理性能還是可以的。
跳過介紹,使用說明點擊跳轉。
插件編寫靈活
簡單的需求在配置文件中完成其實挺不錯的
但是在一些較為復雜的需求面前,配置文件寫出來的可能比較抽象
或者說為了簡化配置就要為某個單獨的需求專門在主項目里寫一段專門用來處理的邏輯,可以是可以,但沒必要。
在使用openresty一段時間后,發現靈活的插件真的會減輕不少的工作量。接下來基于一個相對復雜的小需求來進行插件編寫,點擊跳轉插件示例。
需求:對5分鐘內的訪問狀態碼40x的ip進行針對統計,5分鐘內超過100次的打上標簽鎖定10分鐘,供WAF進行攔截。
這種肯定也可以在waf中寫插件,但是當類似需求多了,那么一條請求處理就可能會產生多次請求,影響waf性能。
這樣的話只讓waf發起一條請求讀取下分析結果就可以直接進行攔截,將工作量轉移給旁路系統,不影響線上服務。
插件秒級生效
在線上環境運行示例風控插件,能涉及到的業務總QPS高峰大概有十萬。
(雖然是背著領導偷偷跑的,但是因為完全旁路于業務,所以問題不大。
插件目前使用主動監測的方式進行更新(說白了,for循環)
但是其實可以使用inotify通過修改事件來驅動插件更新
我這里沒寫是因為我還沒寫完服務端更新的操作,vim編輯保存文件會刪除舊文件創建新文件導致文件監控失敗,有點憨批所以沒搞。
LogFarmer中實時傳日志的方式就是使用事件驅動,實現比較簡單。
插件更新時會自動編譯緩存,供協程調用,避免每次都會要編譯腳本運行。
動圖中演示注釋和運行打印日志方法來檢測插件生效的速度。
靈活自定義的函數庫
以打印日志為例
類型是自定義的access日志GoStruct
豐富的三方依賴支撐
golang能夠使用的所有方法都可以被lua使用,通過如上的定義方式,添加進lua虛擬機供lua使用。
例如樣例lua策略腳本中,使用的redis模塊和方法實際是使用的golang內的redis三方庫。
類型是redis.pipeliner
-- 寫成lua的table是這樣
fbi={
var={
__metatable={
__index=getVarFunctin
}
},
log=logFunction,
ERROR=level_error,
}
內置全局變量
fbi
類型是redis.pipeliner
-- 寫成lua的table是這樣
fbi={
var={
__metatable={
__index=getVarFunctin
}
},
log=logFunction,
ERROR=level_error,
}
內置UserData變量
用于在單個lua協程中傳遞變量
access
類型是redis.pipeliner
pipeline
類型是redis.pipeliner
redis
-- 類型都是lua中的類型。ok是bool類型,err是nil或者string類型,result是string或number類型,str是string類型
-- redis單條請求方法
local redis=require("redis")
-- 方法名都和redis方法類似
local result, err=redis.hmget(key, field)
local ok, err=redis.hmset(key, field, value)
local result, err=redis.incr(key, field)
local ok, err=redis.expire(key, second)
local ok, err=redis.delete(key)
-- redis批量請求方法
local redis=require("redis")
local pipeline=redis.pipeline
-- 新建一個pipeline
pipeline.new()
local result, err=pipeline.hmget(key, field)
local ok, err=pipeline.hmset(key, field, value)
local result, err=pipeline.incr(key, field)
local ok, err=pipeline.expire(key, second)
local ok, err=pipeline.delete(key)
local err=pipeline.exec()
pipeline.close()
re
-- 類型都是lua中的類型。ok是bool類型,err是nil或者string類型,str是string類型
-- 項目在定義給lua用的golang正則方法時,緩存了每個待匹配模式,比如"^ab",提升速度和性能
local re=require("re")
local ok, err=re.match("abcabcd", "^ab")
local str, err=re.find("abcabcd", "^ab")
time
local time=require("time")
local tu=time.unix() -- 時間戳
local tf=time.format() -- 格式化時間 2020-05-31 00:15
local zero=time.zero -- 1590829200, 基準時間,用于跟當前時間做差取余算時間段
說明
目前只寫了kafka的數據輸入,且日志格式為json,后期看情況加。
如需對接自家日志,需要在rule/struct.go中定義下日志格式,可以網上找json2gostrcut的轉換;
再在lua/http.go對照日志struct進行對應參數對接即可。
type AccessLog struct {
Host string `json:"host"` // WAF字段,域名
Status int `json:"status"` // WAF字段,狀態碼
XFF string `json:"XFF"` // WAF字段,X-Forwarded-for
...
}
// 注意下類型就好,lua里面數字都是number類型。
func GetReqVar(L *lua.LState) int {
access :=L.GetGlobal("access").(*lua.LUserData).Value.(*rule.AccessLog)
_=L.CheckAny(1)
switch L.CheckString(2) {
case "host":
L.Push(lua.LString(access.Host))
case "status":
L.Push(lua.LNumber(access.Status))
case "XFF":
L.Push(lua.LString(access.XFF))
...
default:
L.Push(lua.LNil)
}
初次使用可通過打印一些變量來測試,例如
local var=fbi.var
local log=fbi.log
local ERROR=fbi.ERROR
log(ERROR, "status is ", tostring(var.status), ", req is ", var.host, var,uri, "?", var.query)
-- 可能輸出 [error] status is 200, req is www.test.com/path/a?id=1
項目運行流程
按照go.mod里的配置就行
kafka三方庫需要安裝librdkafka,參照
https://github.com/confluentinc/confluent-kafka-go#installing-librdkafka
redis三方庫前幾天剛更新,每個執行函數的參數都加了個ctx,如果不會改的話,go get 7.3版本即可
https://github.com/go-redis/redis/tree/v7
現階段軟件配置
日志源:Kafka
數據緩存:Redis
配置文件樣例
# redis配置
redis: "127.0.0.1:6379"
password: ""
db: 9
# kafka配置
broker: 192.168.1.1:9092
groupid: group-access-test-v1
topic:
- waflog
offset: latest
# 項目日志配置
path: Analyzer.log
使用方式
git clone https://github.com/C4o/FBI-Analyzer
go build main.go
./main
1.如果沒有redis和kafka,沒有關系,修改main.go的最后幾行即可。通過print或log方法進行輸出。
原始代碼
// 初始化redis,連接和健康檢查
red :=db.Redis{
RedisAddr: conf.Cfg.RedAddr,
RedisPass: conf.Cfg.RedPass,
RedisDB: conf.Cfg.DB,
}
// 初始化kafka配置
kaf :=db.Kafka{
Broker: conf.Cfg.Broker,
GroupID: conf.Cfg.GroupID,
Topic: conf.Cfg.Topic,
Offset: conf.Cfg.Offset,
}
// 啟動lua進程
for i :=0; i < runtime.NumCPU(); i++ {
go lua.LuaThread(i)
go kaf.Consumer(lua.Kchan, i)
}
// 本地模擬消費者,不使用kafka
//go lua.TestConsumer()
// redis健康檢查卡住主進程,redis異常斷開程序終止
red.Health()
更新代碼
// 初始化redis,連接和健康檢查
//red :=db.Redis{
// RedisAddr: conf.Cfg.RedAddr,
// RedisPass: conf.Cfg.RedPass,
// RedisDB: conf.Cfg.DB,
//}
// 初始化kafka配置
//kaf :=db.Kafka{
//Broker: conf.Cfg.Broker,
//GroupID: conf.Cfg.GroupID,
//Topic: conf.Cfg.Topic,
//Offset: conf.Cfg.Offset,
//}
// 啟動lua進程
for i :=0; i < runtime.NumCPU(); i++ {
go lua.LuaThread(i)
//go kaf.Consumer(lua.Kchan, i)
}
// 本地模擬消費者,不使用kafka
lua.TestConsumer()
// redis健康檢查卡住主進程,redis異常斷開程序終止
// red.Health()
2.如果模塊或參數使用不對,可在日志中查看lua腳本哪一行報錯。
[root@localhost FBI-Analyzer]# cat Analyzer.log | grep "#" | head -n 5
2020/05/27 13:28:21 [error] Consumer error: 10.205.241.146:9092/bootstrap: Connect to ipv4#10.205.241.146:9092 failed: No route to host (after 4ms in state CONNECT) (<nil>)
2020/05/27 13:41:44 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
2020/05/27 13:41:49 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
2020/05/27 13:41:54 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
2020/05/27 13:41:59 [error] coroutines failed : scripts/counter.lua:5: bad argument #3 to incr (value expected).
WAF體系
攔截中心
項目地址:https://github.com/C4o/IUS
實時日志傳輸模塊
項目地址:https://github.com/C4o/LogFarmer
web安全體系化視頻教程,在線免費觀看!
滲透視頻教程+進群+領工具+靶場
掃碼白嫖!
作者:leviath
轉載自:https://www.freebuf.com/sectool/238366.html
*請認真填寫需求信息,我們會在24小時內與您取得聯系。