【问题标题】:Safely evaluating arithmetic expressions in R?安全地评估 R 中的算术表达式?
【发布时间】:2013-08-24 13:29:24
【问题描述】:

编辑

好的,由于似乎有很多混乱,我将把问题简化一点。您可以尝试回答下面的原始问题,或者您可以改用此版本并忽略该行以下的所有内容。

我的目标是采用任意表达式并在极其受限的环境中对其进行评估。此环境将仅包含具有以下类型值的变量:

  • 数值向量
  • 采用一个或多个数值向量并返回数值向量的纯函数(即算术运算符)

此外,表达式必须能够使用任何文字,例如数字和字符串常量(但不能使用数字或字符串向量,因为它们需要c)。我想在这个环境中评估表达式,并确保表达式无法访问环境之外的任何内容,这样我就可以确定评估表达式不会有安全风险。那么,在下面的代码中,你能用一个在评估时会做一些淘气的字符串来填充空白吗? “一些顽皮的东西”被定义为在屏幕上打印一些东西,访问变量secret 的值,执行任何 shell 命令(最好是产生输出的命令),或任何其他对你来说似乎很顽皮的东西(证明你的选择)。

a <- 1
b <- 2
x <- 5
y <- 1:10
z <- -1

## Give secret a random value so that you can't just compute it from
## the above variables
secret <- rnorm(5)

allowed.variables <- c(
    ## Numeric variables
    "a", "b", "x", "y", "z",
    ## Arithmetic operators
    "(", "+", "-", "/", "*", "^", "sqrt", "log", "log10", "log2", "exp", "log1p")

restricted.environment <- Map(get, allowed.variables)

## Example naughty expressions that my method successfully guards
## against
expr1 <- "secret"
expr2 <- "cat('Printing something with cat\n')"
expr3 <- "system('echo Printing something via shell command')"

arbitrary.expression <- "?????????" # Your naughty string constant here

eval(parse(text=arbitrary.expression), envir=restricted.environment, enclos=emptyenv())

原始问题

我正在编写一些代码来将算术表达式作为用户输入并对其进行评估。我有一组指定的可以使用的变量,以及算术函数的白名单(+-*/^ 等)。有什么方法可以评估表达式,以便只有这些变量和运算符在范围内,以避免任意代码注入的任何可能性?我有一些我认为可行的东西,但我不想实际使用它,除非我确定它确实是防弹的:

## Shortcut for parse-then-eval pattern
evalparse <- function(expr, ...) eval(parse(text=expr), ...)

# I control these
arithmetic.operators <- Map(get, c("(", "+", "-", "/", "*", "^", "sqrt", "log", "log10", "log2", "exp", "log1p"))
vars <- list(a=1, b=2)
safe.envir <- c(vars, arithmetic.operators)

# Assume that these expressions are user input, e.g. from a web form.
nice.expr <- "a + b"
naughty.expr <- paste("cat('ARBITRARY R CODE INJECTION\n'); system('echo ARBITRARY SHELL COMMAND INJECTION');", nice.expr)

## NOT SAFE! Lookups outside env still possible.
evalparse(nice.expr, envir=safe.envir)
evalparse(naughty.expr, envir=safe.envir)

## Is this safe?
evalparse(nice.expr, envir=safe.envir, enclos=emptyenv())
evalparse(naughty.expr, envir=safe.envir, enclos=emptyenv())

如果你在 R 中运行上面的代码,你会看到我们第一次 eval naughty.expr 时,它成功地执行了它的有效载荷。但是,第二次使用enclose=emptyenv(),评估只能访问变量ab 和指定的算术运算符,因此有效负载无法执行。

那么,这种方法(即eval(..., envir=safeenv, enclos=emptyenv()))实际上可以在生产中使用并接受实际的用户输入,还是我错过了一些仍然在受限环境中执行任意代码的偷偷摸摸的方法?

【问题讨论】:

  • 不清楚用户输入的实际内容。用户是否为ab 赋值?他们能控制表达吗?
  • naughty.expr &lt;- "(a+b)*(b+b+a)*(b^b^b+b+a)*((b^(a+b)*(b*b+a)*(b*b*b-a))+a)"
  • 看看hackme.rapporter.net是如何实现沙箱的。
  • @mnel:除了几个人能够打破它。最后我检查了一下,我的消息仍然在你不应该写入的文件中。
  • 对不起,要评估的表达式是用户输入。换句话说,假设naughty.expr 可以是任何 R 代码,并且我只想在它是算术表达式时对其进行评估(仅使用我指定的函数。我在示例代码中添加了注释以澄清.

标签: r evaluation code-injection


【解决方案1】:

我会采用一种稍微不同的方法来定义安全函数和评估任意代码的环境,但这实际上只是一些样式更改。这种技术被证明是安全的,前提是safe_f 中的所有函数都是安全的,即它们不允许您执行任意代码执行。我非常有信心 list 中的函数是安全的,但是您需要检查各个源代码才能确定。

safe_f <- c(
  getGroupMembers("Math"),
  getGroupMembers("Arith"),
  getGroupMembers("Compare"),
  "<-", "{", "("
)

safe_env <- new.env(parent = emptyenv())

for (f in safe_f) {
  safe_env[[f]] <- get(f, "package:base")
}

safe_eval <- function(x) {
  eval(substitute(x), env = safe_env)
}

# Can't access variables outside of that environment
a <- 1
safe_eval(a)    

# But you can create in that environment
safe_eval(a <- 2)
# And retrieve later
safe_eval(a)
# a in the global environment is not affected
a

# You can't access dangerous functions
safe_eval(cat("Hi!"))

# And because function isn't included in the safe list
# you can't even create functions
safe_eval({
  log <- function() {
    stop("Danger!")
  }
  log()
})

这是一个比rapporter sandbox 简单得多的问题,因为您不是试图创建一个有用的 R 环境,只是一个有用的计算器环境,而且要检查的函数集要小得多。

【讨论】:

  • 谢谢。这就是我希望听到的。我担心可能存在某种我不知道的特殊技术来访问任意环境中的值,即使它们不在当前搜索路径上。
  • @RyanThompson 有很多技术,但它们都依赖于您不允许的功能。
  • 是的,但我担心可能有一些晦涩的语法元素不需要任何功能,而只是语言的一部分。但我想 R 语法中的 每个 元素最终只是函数调用的语法糖,这确实是真的。
  • 这很酷。我需要了解如何在 safe_env 中过滤数据框。即使我在允许的函数列表中包含“[”并将数据帧“df”放入安全环境中,诸如:safe_eval(df[1,1]) 之类的东西似乎也不起作用......目标是让用户输入(主要是“比较”功能)指导数据框的过滤。
  • 明确地将 '[.data.frame' 添加到允许的函数列表中解决了这个问题。
猜你喜欢
  • 1970-01-01
  • 2019-06-04
  • 1970-01-01
  • 1970-01-01
  • 2012-03-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多