FortiADC 8.0.0 Script Guide
FortiADC 8.0.0 Script Guide
FortiADC 8.0.0
FORTINET DOCUMENT LIBRARY
[Link]
FORTINET BLOG
[Link]
FORTIGUARD LABS
[Link]
FEEDBACK
Email: techdoc@[Link]
Change Log
Introduction
FortiWeb supports Lua scripts to perform actions that are not currently supported by the built-in feature set. You
can use Lua scripts to write simple, network aware pieces of code that will influence network traffic in a variety
of ways. By using the scripts, you can customize FortiWeb's features by granularly controlling the traffic flow or
even the contents of given sessions or packets.
In FortiWeb, the scripting language only supports HTTP and HTTPS policy.
Configuration overview
You can type or paste the script content into the configuration page.
Before you begin:
l Create a script.
l You must have Read-Write permission for Server Policy settings.
After you have created a script configuration object, you can reference it in the virtual server configuration.
To configure a script:
1. Go to Application Delivery > Scripting.
2. Click Create New to display the configuration editor.
3. Complete the configuration as shown.
Settings Guidelines
Script Events
There are predefined scripts which specify the following events. When the events occur, it will trigger the
system to take the actions defined in the script.
Event priority
FortiWeb supports multiple scripts in one server policy. When a server policy with scripts is enabled, the system
will load scripts one by one. If there are multiple same events defined in the scripts, the event running order is
same as the loading order.
If you want to run a certain event first regardless of the script order, you can define its priority to prioritize its
sequence. The default priority of events is 500. Lower value has higher priority.
For example:
when HTTP_REQUEST priority 499 {
...
package Disabled
coroutine Disabled
table Supported
io Disabled
os Disabled
string Supported
math Supported
utf8 Supported
Global
debug(fmt, ..)
The function is the same as print([Link](fmt, ..)).
The string will be printed to debug log with level 1.
Example
when HTTP_REQUEST {
debug("This HTTP Request method is %s.\n", HTTP:method())
}
_id
This is the id of the proxyd worker running the lua stack.
Example
when HTTP_REQUEST {
debug("id of the proxyd worker running the lua stack is %s.\n", _id)
}
_name
This is the name of the policy running the lua stack.
Example
when HTTP_REQUEST {
debug("name of the proxyd worker running the lua stack is %s.\n", _name)
}
Core
when HTTP_REQUEST
local host = HTTP:host()
[Link](6, "host = %s", host)
}
[Link](level, …)
Similar to print() but allows you to specify the debug log level.
Example:
when HTTP_REQUEST {
local host = HTTP:host()
[Link](6, "host = ", host)
}
Policy
This package is used for fetching the policy configurations.
[Link]()
Return the string of the policy name.
Example
when HTTP_REQUEST {
debug("policy name is %s.\n", [Link]()
}
policy.http_ports()
Return a lua array with all HTTP ports. Port value is integer.
{ 80, 8080 }
Example
when HTTP_REQUEST {
for k,v in pairs(policy.http_ports()) do
debug("http port %s port is %s.\n", k, v)
end
}
policy.https_ports()
Return a lua array with all HTTPS port. Port value is integer.
{ 443, 8443 }
Example
when HTTP_REQUEST {
for k,v in pairs(policy.https_ports()) do
debug("https port %s port is %s.\n", k, v)
end
}
[Link]()
Return lua array with all content routing names.
{ “cr1”, “cr2”, “cr3” }
Example
when HTTP_REQUEST {
for k,v in pairs([Link]()) do
debug("content routing name %s is %s.\n", k, v)
end
}
[Link]() / [Link](“cr-name”)
Return lua array with all servers. If the policy has content routing, the caller should pass the “cr-name” argument
to fetch the servers of the specific content routing.
Example
when HTTP_REQUEST {
for k,v in pairs([Link]()) do
debug("server %s details are %s.\n", k, v)
end
}
IP
This package contains IP related functions.
[Link](“ip-string”)
Generate an IP address class with an IP string.
[Link](“ip-string”) / [Link](ip_class)
Check the reputation of a specific IP. Return Lua array with reputation categories. The reputation categories
are:
"Botnet", "Anonymous Proxy", "Phishing", "Spam", "Others", "Tor"
[Link](“ip-string”) / [Link](ip_class)
Return GEO country name in string. If nothing is found or the IP string is not a valid IP, return nil.
ip.geo_code(“ip-string”) / ip.geo_code(ip_class)
Return GEO country code in string. If nothing is found or the IP string is not a valid IP, return nil.
IP address classes
__eq()
Support use “==” to compare two IP address classes.
__tostring()
Support use tostring(IP-class) to convert IP address class to IP string.
Example
when HTTP_REQUEST {
local ip = tostring(IP:local_addr())
}
IP:local_addr()
Return IP address class, which is the local address of the connection.
Example
when HTTP_REQUEST {
local ip = tostring(IP:local_addr())
if ip == "[Link]" then
IP:remote_addr()
Return IP address class, which is the remote address of the connection.
Example
when HTTP_REQUEST {
local ip = tostring(IP:remote_addr())
if ip == "[Link]" then
debug("remote addr equals to [Link]")
end
}
IP:client_addr()
Return IP address class, which is the client IP address of the stream.
Example
when HTTP_REQUEST {
local ip = tostring(IP:client_addr())
if ip == "[Link]" then
debug("client addr equals to [Link]")
end
}
IP:server_addr()
Return IP address class, which is the server IP address of the stream. If server is not connected, return nil.
Example
when HTTP_REQUEST {
local ip = tostring(IP:server_addr())
if ip == "[Link]" then
debug("server addr equals to [Link]")
end
}
IP:version()
Return the IP version of the connection, either 4 or 6.
Example
when HTTP_REQUEST {
local version = IP:version()
if version == 4 then
debug("ip version is 4")
end
}
Predefined commands
All commands are Lua classes but they only can be used inside scripting events. Some commands can only be
used in specific events. For example, HTTP commands can only be used inside HTTP events (HTTP_
REQUEST and HTTP_RESPONSE).
IP commands
IP commands can be used in HTTP and TCP events.
IP:local_addr()
Return IP address class, which is the local address of the connection.
Example
when HTTP_REQUEST {
local ip = tostring(IP:local_addr())
if ip == "[Link]" then
debug("local addr equals to [Link]")
end
}
IP:remote_addr()
Return IP address class, which is the remote address of the connection.
Example
when HTTP_REQUEST {
local ip = tostring(IP:remote_addr())
if ip == "[Link]" then
debug("remote addr equals to [Link]")
end
}
IP:client_addr()
Return IP address class, which is the client IP address of the stream.
Example
when HTTP_REQUEST {
local ip = tostring(IP:client_addr())
if ip == "[Link]" then
debug("client addr equals to [Link]")
end
}
IP:server_addr()
Return IP address class, which is the server IP address of the stream. If server is not connected, return nil.
Example
when HTTP_REQUEST {
local ip = tostring(IP:server_addr())
if ip == "[Link]" then
debug("server addr equals to [Link]")
end
}
IP:version()
Return the IP version of the connection.
Example
when HTTP_REQUEST {
local version = IP:version()
debug("ip version is %s", version)
}
TCP commands
TCP commands can be used in HTTP and TCP events.
TCP:local_port()
Return local TCP port of the connection. The value is integer.
Example
when HTTP_REQUEST {
print_ips("HTTP_REQUEST", TCP, IP)
}
function print_ips(event, TCP, IP)
debug("%s: version: %s, local: %s:%s, remote: %s:%s, client: %s:%s, server: %s:%s\n",
event, IP:version(),
IP:local_addr(), TCP:local_port(),
IP:remote_addr(), TCP:remote_port(),
IP:client_addr(), TCP:client_port(),
IP:server_addr(), TCP:server_port())
end
TCP:remote_port()
Return remote TCP port of the connection. The value is integer.
Example
when HTTP_REQUEST {
print_ips("HTTP_REQUEST", TCP, IP)
}
function print_ips(event, TCP, IP)
debug("%s: version: %s, local: %s:%s, remote: %s:%s, client: %s:%s, server: %s:%s\n",
event, IP:version(),
IP:local_addr(), TCP:local_port(),
IP:remote_addr(), TCP:remote_port(),
IP:client_addr(), TCP:client_port(),
IP:server_addr(), TCP:server_port())
end
TCP:client_port()
Return client TCP port of the connection. The value is integer.
Example
when HTTP_REQUEST {|
print_ips("HTTP_REQUEST", TCP, IP)
}
TCP:server_port()
Return server TCP port of the connection. The value is integer. If the server is not connected, return nil.
Example
when HTTP_REQUEST {
print_ips("HTTP_REQUEST", TCP, IP)
}
function print_ips(event, TCP, IP)
debug("%s: version: %s, local: %s:%s, remote: %s:%s, client: %s:%s, server: %s:%s\n",
event, IP:version(),
IP:local_addr(), TCP:local_port(),
IP:remote_addr(), TCP:remote_port(),
IP:client_addr(), TCP:client_port(),
IP:server_addr(), TCP:server_port())
end
TCP:close()
Close current TCP connection and disable its TCP events. This function can only be used in event SERVER_
CONNECTED.
Example
when SERVER_CONNECTED {
debug("TCP_CLOSE")
TCP:close()
}
LB commands
LB commands can be used in HTTP events.
LB:routing(“cr-name”)
Example
when HTTP_REQUEST {
local host = HTTP:host()
if startsWith(host, "[Link]") then
LB:routing("cr1")
elseif startsWith(host, "[Link]") then
LB:routing("cr2")
end
}
Examples
Do persistence in HTTP request header:
when HTTP_REQUEST {
local jsession_id = HTTP:cookie("JSESSIONID")
debug("jession_id=%s", jsession_id)
if jsession_id then
debug("jsession_id=%s", jsession_id)
LB:persist(jsession_id)
end
}
when HTTP_REQUEST {
local uri = HTTP:url()
debug("uri=%s", uri)
if [Link](uri, "test_url") then
HTTP:collect()
end
}
when HTTP_DATA_REQUEST {
local body_str = HTTP:body()
local find_sessionID = body_str:find("persist=")
if find_sessionID_2 then
local start_pos = body_str:find("persist=")
local sessionID = body_str:sub(start_pos + 8, start_pos + 8 + 3)
debug("sessionID=%s", sessionID)
LB:persist(sessionID)
end
}
when HTTP_RESPONSE {
local jsession_id = HTTP:cookie("JSESSIONID")
local code, reason = HTTP:status()
debug("code=%s", code)
if jsession_id then
-- if server respone has this cookie
-- record the persistence to the persistence table
debug("jsession_id=%s", jsession_id)
LB:persist(jsession_id)
end
}
when HTTP_RESPONSE {
local code, reason = HTTP:status()
debug("code=%s", code)
if code == "302" then
HTTP:collect()
end
}
when HTTP_DATA_RESPONSE {
local body_str = HTTP:body()
local find_sessionID = body_str:find("persist=")
if find_sessionID then
local start_pos = body_str:find("persist=")
local sessionID = body_str:sub(start_pos + 8, start_pos + 8 + 3)
debug("sessionID=%s", sessionID)
LB: persist(sessionID)
end
}
SSL commands
SSL:sni()
Returns the SNI or false (if no).
This function should be used in script events CLIENTSSL_HANDSHAKE and SERVERSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
local svr_name = SSL:sni()
if svr_name then
debug("client handshake sni: %s\n", svr_name)
end
}
SSL: set_sni(svr_name)
Returns true if the server name indication extension has been set, otherwise false.
This function should be used in the script event SEVERSSL_CLIENTHELLO_SEND.
Example
when SERVERSSL_CLIENTHELLO_SEND {
svr_name = "[Link]"
debug("set Server Name Indication(SNI) in ClientHello = %s\n", svr_name)
SSL:set_sni(svr_name)
}
SSL:cipher()
Returns the cipher in handshake (string type, in OPENSSL form). Please note that the name returned is in
standard RFC format.
Example
when CLIENTSSL_HANDSHAKE {
local cipher = SSL:cipher()
if cipher then
debug("cipher in client handshake =%s\n", cipher)
end
}
SSL:version()
Returns the SSL version in handshake (string type).
This function should be used in script events CLIENTSSL_HANDSHAKE and SERVERSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
local ssl_version = SSL:version()
debug("client ssl version : %s\n", ssl_version)
}
SSL:alpn()
Returns the ALPN protocol selected in handshake (string type). Returns false if not presented or supported.
This function should be used in script events CLIENTSSL_HANDSHAKE and SERVERSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
local alpn_protocol = SSL:alpn()
if alpn_protocol then
debug("alpn_protocol in client handshake = %s\n", alpn_protocol)
end
}
SSL:client_cert_verify()
Returns the status of client-certificate-verify, whether or not it is enabled. True represents enabled, otherwise
False.
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
debug("status of client-certificate-verify = %s", SSL:client_cert_verify())
}
SSL: cert_count()
Returns the total number of certificates that the peer has offered, including the peer certificate and client
certificate chains. (Integer)
Example
when CLIENTSSL_HANDSHAKE {
if SSL:client_cert_verify() then
debug("client cert verify enabled\n")
local cert_cnt = SSL:cert_count()
debug("cert_cnt number %d\n", cert_cnt)
end
}
SSL: get_peer_cert_by_idx(index_value)
Returns the issuer certificate of the index of the X509 SSL certificate in the peer certificate chain, where index is
a value greater than or equal to zero.
A value of zero denotes the first certificate in the chain (aka leaf peer certificate);
A value of one denotes the next, and so on. If the input value is out of range, return nil.
Return type: A table including the information of a client certificate.
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
if SSL:client_cert_verify() then
debug("client cert verify enabled\n")
local cert_cnt = SSL:cert_count()
debug("cert_cnt number %d\n", cert_cnt)
if cert_cnt >= 1 then
local cert_table = SSL:get_peer_cert_by_idx(0)
print_table(cert_table, 0)
end
debug("verify result: %d\n", SSL:verify_result())
end
}
-- a function to print a table, i represents the number of \t for formatting purpose.
function print_table(table, indent)
local space = [Link]('\t',indent)
for key, value in pairs(table) do
if(type(value)=='table') then
debug("%s sub-table[%s]\n", space, key)
print_table(value, indent+1)
else
debug("%s %s: %s\n", space, key, value)
end
end
end
SSL: verify_result()
Returns the result code from peer certificate verification. The returned code uses the same values as those of
OpenSSL’s X509 verify_result (X509_V_ERR_) definitions.
Returns type: Integer. Returns -1 if the verification code can not be retrieved
This function should be ONLY used in script event CLIENTSSL_HANDSHAKE.
Example
when CLIENTSSL_HANDSHAKE {
if SSL:client_cert_verify() then
debug("client cert verify enabled\n")
debug("verify result: %d\n", SSL:verify_result())
end
}
SSL Renegotiate
SSL_RENEGOTIATE()
When the system evaluates the command under a client-side context, the system immediately renegotiates a
request for the associated client-side connection.
This function is temporarily ONLY available in HTTP_REQUEST event.
It returns true for success and false for failure.
This function does not support TLS1.3.
Example
In this sample script, when an HTTPS request with the prefix "autotest" is received, it triggers client certificate
verification through SSL renegotiation.
Once the SSL renegotiation is completed, it checks the content-routing policy.
If the client certificate presented by the client meets certain conditions that matches a specific HTTP content
routing policy, the traffic will be directed to a designated server pool.
The following is a function to print a table, i represents the number of \t for formatting purpose.
else
debug("%s %s: %s\n", space, key, value)
end
end
end
when HTTP_REQUEST {
local url = HTTP:url()
if url:find("^/autotest") and HTTP:is_https() and SSL:client_cert_verify() then
-- Trigger SSL renegotiate only when it's https request and SSL connection has
already been established
-- Example URL-based certificate verify and then Content-Routing
debug("url: %s match rule, need client certificate verify\n", url)
local cert_count = SSL:cert_count()
debug("cert_count = %s\n", cert_count)
if cert_count and cert_count == 0 then
SSL:renegotiate()
debug("emit SSL renegotiation\n")
end
end
}
when CLIENTSSL_RENEGOTIATE {
local cert_count = SSL:cert_count()
debug("cert_count = %s\n", cert_count)
if cert_count and cert_count > 0 then
local cert_table = SSL:get_peer_cert_by_idx(0)
print_table(cert_table, 0)
local subject = cert_table["subject"]
-- match CN value with regular expression
local cn_value = subject:match("CN%s-=%s-([^,%s]+)")
debug("CN value in X509 subject is: %s\n", cn_value)
if cn_value and cn_value == "test1" then
LB:routing("ctrt")
end
end
}
SSL:session(t) [TODO]
Allows you to get SSL session id / reused / remove from cache.
Input t is a table, with a key “operation”, and there will be three choices: “get_id” or “remove” or “reused”.
Return string for get operation, and boolean for remove or reused operation.
This function should be used in script events CLIENTSSL_HANDSHAKE and SERVERSSL_HANDSHAKE.
HTTP Commands
HTTP commands can be used in HTTP events.
Header fetch
HTTP:headers()
Fetch all HTTP request or response headers. When it is called in client side, it returns all HTTP request
headers; When it is called in server side, it returns all HTTP response headers.
Return: lua table of arrays.
Example
when HTTP_REQUEST {
for k, v in pairs(HTTP:headers()) do
for i = 1, #v do
debug("HEADER: %s[%d]: %s\n", k, i, v[i])
end
end
}
HTTP:header(“header-name”)
Fetch specific HTTP request or response header.
Return: lua array.
Example
when HTTP_RESPONSE {
for i, v in ipairs(HTTP:header("set-cookie")) do
debug("set-cookie[%d]: %s\n", i, v)
end
}
HTTP:cookies()
Fetch all cookies. When it is called in client side, it fetches “Cookies”; When it is called in server side, it fetches
“Set-Cookie”.
Example
when HTTP_REQUEST {
for k, v in pairs(HTTP:cookies()) do
debug("Cookie: %s = %s\n", k, v)
end
}
HTTP:cookie(“cookie-name”)
Fetch the value of specific cookies.
Return: string.
Example
when HTTP_REQUEST {
persist = HTTP:cookie("persist")
}
HTTP:args()
Fetch all arguments of HTTP query.
Return: lua table containing key and value.
Example
when HTTP_REQUEST {
for k, v in pairs(HTTP:args()) do
debug("ARG: %s = %s\n", k, v)
end
}
HTTP:arg(“arg-name”)
Fetch the value of specific arguments.
Return: string.
Example
when HTTP_REQUEST {
v = HTTP:arg("ip")
}
HTTP:host()
Return the string of HTTP request host.
Example
Request : [Link]
when HTTP_REQUEST {
local host = HTTP:host()
if host == "[Link]" then
debug("host = %s", host)
end
}
Output: [Link]
HTTP:url()
Return the string of HTTP request URL. It is the full URL including path and query.
Example
when HTTP_REQUEST {
local url = HTTP:url()
if url == "/[Link]?id=1234" then
debug("url = %s", url)
end
}
HTTP:path()
Return the string of the HTTP request path.
Example
when HTTP_REQUEST {
local path = HTTP:path()
if path == "/[Link]" then
debug("path = %s", path)
end
}
HTTP:method()
Return the string of HTTP request method.
Example
when HTTP_REQUEST {
local method = HTTP:method()
debug("method = %s", method)
if method == "GET" then
debug("method = %s", method)
end
}
HTTP:version()
Return the string of HTTP request or response version.
Example
when HTTP_RESPONSE {
debug("http version = %s", HTTP:version())
}
HTTP:status()
Return two strings including HTTP response status code and reason.
code, reason = HTTP:status()
Example
when HTTP_RESPONSE {
code, reason = HTTP:status()
if code == "200" then
debug("code = 200, reason = %s", reason)
end
}
Header manipulate
HTTP:set_path(“new-path”)
Change the path in HTTP request header.
Return true for success and false for failure.
Example
when HTTP_REQUEST {
HTTP:set_path("/new_path")
}
HTTP:set_query(“new-query”)
Change the query in HTTP request header.
Return true for success and false for failure.
Example
when HTTP_REQUEST {
HTTP:set_query("test=1")
}
HTTP:set_url(“new-url”)
Change the whole URL, including the path and query.
Return true for success and false for failure.
Example
when HTTP_REQUEST {
HTTP:set_url("/new_path?test=1")
}
HTTP:set_method(“new-method”)
Change the method in HTTP request header.
Return true for success and false for failure.
Example
when HTTP_REQUEST {
HTTP:set_method("POST")
}
Example
when HTTP_RESPONSE {
HTTP:set_status(200, "Other Reason")
}
HTTP:add_header(“header-name”, “header-value”)
Add a header line to HTTP request or response header.
Return true for success and false for failure.
Example
HTTP:del_header(“header-name”)
Remove the header with name “header-name” from HTTP request or response.
Return true for success and false for failure.
Example
HTTP:set_header(“header-name”, header-value-array)
Remove the header with name “header-name” from HTTP request or response, and add this header with new
value header-value-array. The argument header-value-array is a Lua array which is the value got from
HTTP:header().
Return true for success and false for failure.
Example
Example
Custom reply
These functions only can be used in HTTP client side event (only HTTP_REQUEST now).
HTTP:redirect (“fmt”, …)
Reply to client with redirect response.
Example
when HTTP_REQUEST {
HTTP:redirect(“[Link] HTTP:host())
}
HTTP:reply (response)
Reply to client with custom response.
Argument response is a lua array. It includes:
l status: Integer. Default is 200.
l reason: String. If not set, the system will use the default value of status code. For example, if the status
code is 200, the default value of reason is “OK”.
l headers: Lua table. Each value of the table is a lua array. It contains all headers except “content-length”.
“content-length” will be automatically set with the body size.
l Body: String.
To be specific:
HTTP:reply{
status = 400,
reason = “test reason”,
headers = {
["content-type"] = { "text/html" },
["cache-control"] = { "no-cache", "no-store" },
},
body = "<html><body><h1>invalid request<h1></body></html>",
}
Example
function reply_invalid(HTTP)
HTTP:reply{
status = 400,
headers = {
["content-type"] = { "text/html" },
["cache-control"] = { "no-cache", "no-store" },
},
body = "<html><body><h1>Invalid API Request<h1></body></html>",
}
end
function check_ip_reputation(HTTP)
local v = HTTP:arg("ip")
if v then v = [Link](v) end -- convert string to ip
if not v then return reply_invalid(HTTP) end
local r = [Link](v)
local body = [Link]("<html><body><h1>Reputation of IP %s: %s<h1></body></html>",
v, #r > 0 and [Link](r, ', ') or "No Found")
HTTP:reply{
status = 200,
headers = {
["content-type"] = { "text/html" },
["cache-control"] = { "no-cache", "no-store" },
},
body = body,
}
end
function check_ip_geo(HTTP)
local v = HTTP:arg("ip")
if v then v = [Link](v) end -- convert string to ip
if not v then return reply_invalid(HTTP) end
local geo = [Link](v)
local geo_code = ip.geo_code(v)
local body = [Link]("<html><body><h1>GEO of IP %s: %s, code:
%s<h1></body></html>",
v, geo, geo_code)
HTTP:reply{
status = 200,
headers = {
["content-type"] = { "text/html" },
["cache-control"] = { "no-cache", "no-store" },
},
body = body,
}
end
when RULE_INIT {
actions = {}
actions["reputation"] = check_ip_reputation
actions["geo"] = check_ip_geo
convert = {}
convert["testing"] = "test"
convert["debugging"] = "debug"
whitelist = {}
whitelist["test"] = true
whitelist["debug"] = true
whitelist["others"] = true
}
when HTTP_REQUEST {
local path = HTTP:path()
path = path:gsub("^/api/", "/api2/") -- convert /api/ to /api2/
local api = path:match("^/api2/(.+)") -- get api string that is after /api2/
-- check api
if api then
if actions[api] then -- if api is in table "actions", run function
return actions[api](HTTP)
end
if convert[api] then -- if api is in table "convert", convert api
api = convert[api] -- change to new api
end
if not whitelist[api] then -- if api is not in whitelist, reply invalid
return reply_invalid(HTTP)
end
HTTP:set_path("/api2/" .. api) -- pass the api to server
return
end
-- if path doesn't starts with /api or /api2, do nothing
}
Control
HTTP:close()
Close the current HTTP transaction and disable its HTTP events. This function can only be used in event
HTTP_REQUEST.
Note the following:
l Close the current HTTP transaction and disable its HTTP events.
l This function can only be used in event HTTP_REQUEST.
l The code logic in the HTTP_REQUEST event will be executed and then close the http connection no
matter where method HTTP:close() is.
Example
when HTTP_REQUEST {
local path = HTTP:path()
HTTP:close()
local url = HTTP:url()
}
Protocol
HTTP:is_https()
Return true if the current transaction is in HTTPS connection.
Example:
when HTTP_REQUEST {
debug("current transaction is HTTPS connections: %s", HTTP:is_https())
}
In FortiWeb, sometimes a private data is needed for HTTP transaction, and the value is shared in the same
HTTP transaction.
HTTP:setpriv(object)
Store a lua object as the HTTP transaction private data. You can store a lua object in event HTTP_REQUEST
and fetch it by calling HTTP:priv() in event HTTP_RESPONSE.
Example
when HTTP_REQUEST {
store_data = "test"
HTTP:setpriv(store_data)
}
when HTTP_RESPONSE {
debug("stored_data = %s", HTTP:priv())
}
HTTP:priv()
Fetch the transaction private data that stored by HTTP:setpriv(). If no result is found, it will return an empty lua
table.
when HTTP_REQUEST {
store_data = "test"
HTTP:setpriv(store_data)
}
when HTTP_RESPONSE {
debug("stored_data = %s", HTTP:priv())
}
Data Collect
HTTP:collect()
HTTP:collect() function instructs FortiWeb to buffer and make available the HTTP request or response body
for inspection in subsequent script events. This function can only be used in the HTTP_REQUEST and HTTP_
RESPONSE events.
Syntax
HTTP:collect(size)
Availability
when HTTP_REQUEST {
if HTTP:header(“content-type”) == text/css
HTTP::collect()
end
}
when HTTP_DATA_REQUEST {
local body_str = HTTP:body()
debug("body = %s", body_str)
}
when HTTP_REQUEST {
HTTP:collect(32) -- collect first 32 bytes of the body
}
when HTTP_DATA_REQUEST {
local body_sample = HTTP:body(0, 32)
if body_sample then
debug("partial collect body information, partial body = %s", body sample)
end
}
Notes
l Specifying a partial size can help reduce latency or processing overhead when only a small portion of the
body is needed to make decisions.
l When partial collection is used, the HTTP_DATA_REQUEST or HTTP_DATA_RESPONSE event is triggered as
soon as the specified number of bytes is available.
l This function is often used in conjunction with:
l HTTP:body(offset, length)
l debug() logging
Body Rewrite
Example:
when HTTP_REQUEST {
HTTP:collect()
}
--This function will change "username:test" to "username:Test"
function username_first_char_uppercase(str)
local str1 = str:sub(1, 9)
local str2 = str:sub(10, 10)
str2 = str2:upper()
local str3 = str:sub(11, -1)
return str1..str2..str3
end
when HTTP_DATA_REQUEST {
local body_str = HTTP:body(0, 16)
local body_new = body_str:gsub("username:[A-Za-z][A-Za-z0-9_]+", username_first_char_
uppercase)
debug("body old = %s, body new = %s\n", body_str, body_new)
HTTP:set_body(body_new, 0, 16)
}
Example
when HTTP_REQUEST {
HTTP:collect()
}
--This function will change "username:test" to "username:Test"
function username_first_char_uppercase(str)
local str1 = str:sub(1, 9)
local str2 = str:sub(10, 10)
str2 = str2:upper()
local str3 = str:sub(11, -1)
return str1..str2..str3
end
when HTTP_DATA_REQUEST {
local body_str = HTTP:body(0, 16)
local body_new = body_str:gsub("username:[A-Za-z][A-Za-z0-9_]+", username_first_char_
uppercase)
debug("body old = %s, body new = %s\n", body_str, body_new)
HTTP:set_body(body_new, 0, 16)
}
SSL:close()
Terminates the SSL/TLS connection during the handshake phase, allowing FortiWeb to enforce early-session security
decisions based on SSL context. This function is particularly useful in scenarios where you need to prevent connections
from proceeding beyond the SSL layer, such as when rejecting traffic based on the Server Name Indication (SNI) value
or other handshake metadata before HTTP parsing or WAF processing occurs.
Internally, SSL:close() triggers a connection teardown by sending a TCP FIN or RST (depending on timing and state)
without completing the handshake or generating application-level logs. Because the TLS handshake is aborted, this
function minimizes resource usage and ensures the transaction is dropped silently from the client’s perspective.
This function can only be used in the CLIENTSSL_HANDSHAKE or SERVERSSL_HANDSHAKE script events, where SSL-
specific inspection (e.g., SNI retrieval via SSL:sni()) is supported.
Syntax
SSL:close()
Availability
Behavior
l Terminates the SSL connection immediately, preventing further processing (including HTTP and WAF modules).
l Can be combined with functions like SSL:sni() to enforce domain-level access control.
l The connection is dropped silently without alerting the client (no TLS alerts or HTTP response).
when CLIENTSSL_HANDSHAKE {
local svr_name = SSL:sni()
if svr_name == "[Link]" then
SSL:close()
debug("Blocked connection with SNI: %s\n", svr_name)
end
}
when SERVERSSL_HANDSHAKE {
local svr_name = SSL:sni()
if svr_name == "[Link]" then
SSL:close()
debug("Terminating server handshake for internal domain: %s\n", svr_name)
SSL_RENEGOTIATE()
When the system evaluates the command under a client-side context, the system immediately renegotiates a request
for the associated client-side connection. This function is temporarily ONLY available in HTTP_REQUEST event.
Return true for success and false for failure.
Example
In this sample script, when an HTTPS request with the prefix "autotest" is received, it triggers client certificate
verification through SSL renegotiation.
Once the SSL renegotiation is completed, it checks the content-routing policy.
If the client certificate presented by the client meets certain conditions that matches a specific HTTP content routing
policy, the traffic will be directed to a designated server pool.
--
#a function to print a table, i represents the number of \t for formatting purpose.
function print_table(table, indent)
local space = [Link]('\t',indent)
for key, value in pairs(table) do
if(type(value)=='table') then
debug("%s sub-table[%s]\n", space, key)
print_table(value, indent+1)
else
debug("%s %s: %s\n", space, key, value)
end
end
end
when HTTP_REQUEST {
local url = HTTP:url()
if url:find("^/autotest") and HTTP:is_https() and SSL:client_cert_verify() then
-- Trigger SSL renegotiate only when it's https request and SSL connection has already
been established
-- Example URL-based certificate verify and then Content-Routing
debug("url: %s match rule, need client certificate verify\n", url)
local cert_count = SSL:cert_count()
debug("cert_count = %s\n", cert_count)
if cert_count and cert_count == 0 then
SSL:renegotiate()
debug("emit SSL renegotiation\n")
end
end
}
when CLIENTSSL_RENEGOTIATE {
local cert_count = SSL:cert_count()
HTTP:skip_waf()
Use this Lua script to instruct FortiWeb to bypass WAF module inspection based on HTTP request or response content.
This is especially useful when specific URL patterns, headers, or body contents are known to be safe but would
otherwise be flagged by WAF rules.
Function:
HTTP:skip_waf()
Supported Events:
l HTTP_REQUEST, HTTP_RESPONSE: Skips all WAF modules(except for layer3 session level checked WAF
modules)
l HTTP_DATA_REQUEST, HTTP_DATA_RESPONSE (partial): Skips follow-up WAF modules if HTTP:collect(N)
was used.
l HTTP_DATA_* (full body): Skips remaining modules after LUA_body module follow-up processing.
Example:
when HTTP_REQUEST {
HTTP:collect(32)
}
when HTTP_DATA_REQUEST {
local url = HTTP:url()
if url and url:find("^/webaccess/") then
local body = HTTP:body(0, 32)
if body and body:find("^MUXV2 / HTTP/1.0") then
HTTP:skip_waf()
debug("Bypassing WAF for MUXV2 protocol\n")
end
end
}
In some deployment scenarios (e.g., VMware BLAST protocol), specific HTTP requests should bypass WAF inspection
to avoid false positives and performance degradation. The following script skips WAF processing for POST requests to
the /ice/tunnel endpoint if they use chunked transfer encoding:
when HTTP_REQUEST {
local method = HTTP:method()
local uri = HTTP:url()
local transfer_enc = HTTP:header("Transfer-Encoding")
This logic helps prevent misclassification of tunneled traffic and ensures compatibility with applications using streaming
protocols.
Use Cases
when HTTP_REQUEST {
if not HTTP:is_https() then
local host = HTTP:header("host")[1]
local https_port = policy.https_ports()[1] -- get the first port in HTTP service
local newhost = host:gsub(":(%d+)", "") -- remove port from host if it has
if https_port ~= 443 then
-- if https port is not 443, add port to host
newhost = newhost .. ":" .. tostring(https_port)
end
HTTP:redirect("[Link] newhost, HTTP:url())
end
function extract_xff(xff)
local t = {}
local k, v, s
for k, v in ipairs(xff) do
for s in v:gmatch("([^,]+)") do
t[#t + 1] = s:gsub("%s+", "")
end
end
return t
end
when HTTP_REQUEST {
local ips = extract_xff(HTTP:header("X-Forwarded-For"))
local r, i, v
for i, v in ipairs(ips) do
r = [Link](v) -- check ip, will return an array
if #r > 0 then -- Found IP in reputation database
debug("Found bad IP %s in XFF headers, reputation: <%s>, GEO country: <%s>, GEO
country code: %s\n",
v, [Link](r, ', '),
[Link](v) or "unknown", ip.geo_code(v) or "unknown")
HTTP:close() -- force close this HTTP connection
return -- Stop script and return
end
end
}
when HTTP_REQUEST {
local ip_addresses = {"[Link]", "[Link]", "[Link]"}
local skipIPs = {}
for _, ip in ipairs(ip_addresses) do
skipIPs[ip] = true
end
local url = HTTP:url()
local ip = tostring(IP:client_addr())
debug("url = %s, ip = %s, contains = %s ", url, ip, skipIPs[ip])
if skipIPs[ip] == nil and url == "/autotest/[Link]" then
debug("redirect")
HTTP:redirect("[Link] HTTP:host())
end
}
rand()
Generates a random number, returns an integer value between 0 and RAND_MAX(2^31-1).
Example
when HTTP_REQUEST {
local rand_num = rand()
debug("rand_num=%d\n",rand_num)
}
time()
Returns the current time as an integer, in Unix time format.
Example
when HTTP_REQUEST {
local now = time()
debug("time now = %d\n", now)
}
time_ms()
Returns the current time in million seconds, in Unix time format
Example
when HTTP_REQUEST {
local now_ms = time_ms()
debug("time now in million seconds = %d\n", now_ms)
}
ctime()
Returns the current time as a string, For instance Thu Apr 15 09:01:46 2024 CST +0800
Example
when HTTP_REQUEST {
local now_str = ctime()
debug("time now in string format: %s\n", now_str)
}
md5(input_msg)
Calculates the MD5 hash of a given string input and returns the result as a string.
Example
The following is a helper function to convert byte string into hex representation.
function bytes2hex(bytestr)
local hexString = ""
for i = 1, [Link](bytestr) do
hexString = hexString .. [Link]("%02x", [Link](bytestr, i))
end
return hexString
end
when HTTP_REQUEST {
local md5_encrypted = md5_str("123")
debug("length of md5_encrypted is %d \n", [Link](md5_encrypted))
debug("encrypted md5 of string 123 is: %s\n", bytes2hex(md5_encrypted))
}
md5_hex_str(input_msg)
Calculates the hex representation of the MD5 of a string, and returns the result as a string.
Example
when HTTP_REQUEST {
local md5_encrypted_hex = md5_hex_str("123")
debug("encrypted md5 of string 123 in hex representation is: %s\n", md5_encrypted_hex)
}
sha1_str(input_msg)
Calculates the SHA1 of a string input, and returns the result as a string.
Example
The following is a helper function to convert byte string into hex representation.
function bytes2hex(bytestr)
local hexString = ""
for i = 1, [Link](bytestr) do
hexString = hexString .. [Link]("%02x", [Link](bytestr, i))
end
return hexString
end
when HTTP_REQUEST {
local sha1_123 = sha1_str("123")
debug("length of sha1_123 is %d \n", [Link](sha1_123))
debug("encrypted sha1 of string 123 is: %s\n", bytes2hex(sha1_123))
}
sha1_123_hex(input_msg)
Calculates the hex representation of SHA1 of a string input, and returns the result as a string.
Example
when HTTP_REQUEST {
local sha1_123_hex = sha1_hex_str("123")
debug("encrypted sha1 of string 123 in hex representation is: %s\n", sha1_123_hex)
}
sha256_str(input_msg)
Calculates the SHA256 of a string input, and returns the result as a string.
Example
The following is a helper function to convert byte string into hex representation.
function bytes2hex(bytestr)
local hexString = ""
for i = 1, [Link](bytestr) do
hexString = hexString .. [Link]("%02x", [Link](bytestr, i))
end
return hexString
end
when HTTP_REQUEST {
local sha256_123 = sha256_str("123")
debug("length of sha256_123 is %d \n", [Link](sha256_123))
debug("encrypted sha256 of string 123 is: %s\n", bytes2hex(sha256_123))
}
sha256_hex_str(input_msg)
Calculates the hex representation of SHA1 of a string input, and return the result as a string.
Example
when HTTP_REQUEST {
local sha256_123_hex = sha256_hex_str("123")
debug("encrypted sha256 of string 123 in hex representation is: %s\n", sha256_123_hex)
}
sha512_123(input_msg)
Calculates the SHA512 of a string input, and returns the result as a string.
Example
The following is a helper function to convert byte string into hex representation.
function bytes2hex(bytestr)
local hexString = ""
for i = 1, [Link](bytestr) do
hexString = hexString .. [Link]("%02x", [Link](bytestr, i))
end
return hexString
end
when HTTP_REQUEST {
local sha512_123 = sha512_str("123")
debug("length of sha512_123 is %d \n", [Link](sha512_123))
debug("encrypted sha512 of string 123 is: %s\n", bytes2hex(sha512_123))
}
sha512_123_hex(input_msg)
Calculates the hex representation of SHA1 of a string input, and returns the result in string representation.
Example
when HTTP_REQUEST {
local sha512_123_hex = sha512_hex_str("123")
debug("encrypted sha512 of string 123 in hex representation is: %s\n", sha512_123_hex)
}
base64_enc(input_msg)
Encodes a string input in base64 and outputs the results in string format.
Example
when HTTP_REQUEST {
local b64_msg = base64_enc("[Link]
debug("base64 encoded message is: %s\n", b64_msg)
}
base64_dec(input_msg)
Decodes a base64 encoded string input and outputs the results in string format.
Example
when HTTP_REQUEST {
local b64_dec_msg = base64_dec(b64_msg)
debug("base64 decoded message is: %s\n", b64_dec_msg)
}
base32_enc(input_msg)
Encodes a string input in base32 and outputs the results in string format.
Example
when HTTP_REQUEST {
local b32_msg = base32_enc("[Link]
debug("base32 encoded message is: %s\n", b32_msg)
}
base32_dec(input_msg)
Decodes a base32 encoded string input and outputs the results in string format.
Example
when HTTP_REQUEST {
local b32_dec_msg = base32_dec(b32_msg)
debug("base32 decoded message is: %s\n", b32_dec_msg)
}
htonl(input_msg)
Converts a long integer input into network byte order.
Example
when HTTP_REQUEST {
local network_a = htonl(32)
debug("htonl of 32 is: %s\n", network_a)
}
htons(input_msg)
Converts a short integer input into network byte order.
Example
when HTTP_REQUEST {
local network_a_short = htons(32)
debug("htons of 32 is: %s\n", network_a_short)
}
htons(input_msg)
Converts a long integer input into host byte order. Keep in mind, htonl(ntohl(x)) == x.
Example
when HTTP_REQUEST {
local host_a = ntohl(network_a)
debug("ntohl of network_a is: %s\n", host_a)
}
host_a_short(input_msg)
Converts a short integer input into host byte order.
Example
when HTTP_REQUEST {
local host_a_short = ntohs(network_a_short)
debug("ntohs of network_a_short is: %s\n", host_a_short)
}
to_hex(input_msg)
Converts a string to its hex representation.
Example
when HTTP_REQUEST {
local hexit = to_hex("it")
debug("hexit is: %s\n", hexit)
}
crc32(input_msg)
Returns the crc32 check value of the string, return value is the crc32 code.
Example
when HTTP_REQUEST {
local crc32_code = crc32("123456789")
debug("CRC 32 code is: %d\n", crc32_code)
}
Example
The following is a helper function to convert byte string into hex representation.
function bytes2hex(bytestr)
local hexString = ""
for i = 1, [Link](bytestr) do
hexString = hexString .. [Link]("%02x", [Link](bytestr, i))
end
return hexString
end
when HTTP_REQUEST {
local new_key = key_gen("pass", "salt", 32, 32)
debug("new key is %s\n", bytes2hex(new_key))
}
Example
The following is a helper function to convert byte string into hex representation.
when HTTP_REQUEST {
local aes_encrypted = aes_enc("your message", "paste your key here", 128)
debug("encrypted in hex is %s, after b64 encoding %s\n", to_hex(aes_encrypted), base64_enc
(aes_encrypted))
}
Example
when HTTP_REQUEST {
local aes_decrypted = aes_dec(aes_encrypted, "paste your key here", 128);
debug("decrypted msg is %s\n", aes_decrypted)
}
EVP_Digest(alg, str)
EVP_Digest(alg, str) EVP_Digest for one-shot digest calculation.
Example
when HTTP_REQUEST {
local evpd = EVP_Digest("MD5", "your data")
debug("the digest in hex is %s\n", bytes2hex(evpd))
}
Example
when HTTP_REQUEST {
local hm = HMAC("SHA256", "your data", "paste your key here")
debug("the HMAC in hex is %s\n", bytes2hex(hm))
}
Example
when HTTP_REQUEST {
local is_same = HMAC_verify("SHA256", "your data", "paste your key here", hm)
if is_same then
debug("HMAC verified\n")
else
debug("HMAC not verified\n")
end
}
rand_hex(input)
Generates a random number in HEX.
Example
when HTTP_REQUEST {
local rand_h = rand_hex(16);
debug("the random hex number is %s\n", rand_h);
}
rand_alphanum(input)
Generates a random alphabet+number sequence.
Example
when HTTP_REQUEST {
local alphanumber = rand_alphanum(16);
debug("the alphabet+number sequence is %s\n", alphanumber);
}
rand_seq(input)
Generates a random number sequence.
Example
when HTTP_REQUEST {
local randseq = rand_seq(16);
debug("the random sequence is %s\n", to_hex(randseq));
}
url_encode(input)
Encodes the target URL (Converts URL into a valid ASCII format, will not replace space by "+" sign).
Example
when HTTP_REQUEST {
local encoded_url = url_encode("[Link]
debug("the encoded url is %s\n", encoded_url);
}
url_decode(input)
Decodes the encoding-URL into its original URL.
Example
when HTTP_REQUEST {
local decoded_url = url_decode(encoded_url);
debug("the decoded url is %s\n", decoded_url);
}
Copyright© 2025 Fortinet, Inc. All rights reserved. Fortinet®, FortiGate®, FortiCare® and FortiGuard®, and certain other marks are registered trademarks of Fortinet, Inc., and other Fortinet names herein
may also be registered and/or common law trademarks of Fortinet. All other product or company names may be trademarks of their respective owners. Performance and other metrics contained herein
were attained in internal lab tests under ideal conditions, and actual performance and other results may vary. Network variables, different network environments and other conditions may affect performance
results. Nothing herein represents any binding commitment by Fortinet, and Fortinet disclaims all warranties, whether express or implied, except to the extent Fortinet enters a binding written contract,
signed by Fortinet’s Chief Legal Officer, with a purchaser that expressly warrants that the identified product will perform according to certain expressly-identified performance metrics and, in such event,
only the specific performance metrics expressly identified in such binding written contract shall be binding on Fortinet. For absolute clarity, any such warranty will be limited to performance in the same ideal
conditions as in Fortinet’s internal lab tests. Fortinet disclaims in full any covenants, representations, and guarantees pursuant hereto, whether express or implied. Fortinet reserves the right to change,
modify, transfer, or otherwise revise this publication without notice, and the most current version of the publication shall be applicable.