Module: PWN::Plugins::AISandbox

Defined in:
lib/pwn/plugins/ai_sandbox.rb

Overview

Forked worker for model-authored shell/Ruby with Landlock/seccomp profiles.

Constant Summary collapse

WRITE_RX =
%r{\b(?:rm|rmdir|unlink|shred|truncate)\b|FileUtils\.rm|File\.(?:delete|unlink)|Pathname.*rmtree|>\s*/}
NET_RX =
/\b(?:curl|wget|nc|ncat|ssh|scp|ftp|telnet)\b|TCPSocket|UDPSocket|Net::HTTP|Socket\.(?:tcp|udp)/
PATH_RX =
%r{FileUtils\.\w+\(\s*['"]([^'"]+)['"]|(?:^|\s)(?:rm|rmdir)\s+(?:-[a-zA-Z]+\s+)*([^\s;|&]+)|['"](/(?:[^'"]+))['"]}

Class Method Summary collapse

Class Method Details

.authorsObject



64
65
66
# File 'lib/pwn/plugins/ai_sandbox.rb', line 64

public_class_method def self.authors
  "AUTHOR(S):\n  0day Inc. <[email protected]>\n"
end

.classify(opts = {}) ⇒ Object

Classify payload as read, write, or network for profile selection.



30
31
32
33
34
35
36
37
# File 'lib/pwn/plugins/ai_sandbox.rb', line 30

public_class_method def self.classify(opts = {})
  text = (opts[:payload] || opts[:command] || opts[:code]).to_s
  return (opts[:side_effect] || opts[:class]).to_sym if opts[:side_effect] || opts[:class]
  return :write if text.match?(WRITE_RX)
  return :network if text.match?(NET_RX)

  :read
end

.exec(opts = {}) ⇒ Object

Run shell or Ruby in a forked worker under the selected profile.



40
41
42
43
44
45
46
47
48
# File 'lib/pwn/plugins/ai_sandbox.rb', line 40

public_class_method def self.exec(opts = {})
  kind = (opts[:kind] || :shell).to_s.to_sym
  payload = (opts[:payload] || opts[:command] || opts[:code]).to_s
  profile = classify(opts.merge(payload: payload))
  denied = preflight(opts.merge(payload: payload, profile: profile))
  return denied if denied

  run_worker(opts.merge(kind: kind, payload: payload, profile: profile))
end

.helpObject



68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/pwn/plugins/ai_sandbox.rb', line 68

public_class_method def self.help
  puts "USAGE:
    # List host binaries this module expects to be installed.
    #{self}.required_bins

    # Read the ai_sandbox key as strict, permissive, or off.
    #{self}.mode(
      mode: 'optional - strict, permissive, or off overlay',
      ai_sandbox: 'optional - alias for mode'
    )

    # Classify payload as read, write, or network for profile selection.
    #{self}.classify(
      payload: 'optional - shell command or Ruby source',
      command: 'optional - alias for payload',
      code: 'optional - alias for payload',
      side_effect: 'optional - declared class read, write, or network',
      class: 'optional - alias for side_effect'
    )

    # Run shell or Ruby in a forked worker under the selected profile.
    #{self}.exec(
      kind: 'optional - shell or ruby (defaults to shell)',
      payload: 'optional - command or Ruby source',
      command: 'optional - alias for payload',
      code: 'optional - alias for payload',
      mode: 'optional - strict, permissive, or off overlay',
      timeout: 'optional - worker seconds budget',
      side_effect: 'optional - declared class read, write, or network',
      scope: 'optional - Array of hostnames allowed for network egress'
    )

    # Intercept shell when ai_sandbox is not off; nil means the caller runs host Open3.
    #{self}.wrap_shell(
      command: 'optional - shell command to sandbox',
      payload: 'optional - alias for command',
      mode: 'optional - strict, permissive, or off overlay',
      timeout: 'optional - worker seconds budget'
    )

    # Intercept pwn_eval when ai_sandbox is not off; nil means in-process eval.
    #{self}.wrap_ruby(
      code: 'optional - Ruby source to sandbox',
      payload: 'optional - alias for code',
      mode: 'optional - strict, permissive, or off overlay',
      timeout: 'optional - worker seconds budget'
    )

    # Print the AUTHOR(S) string for this module.
    #{self}.authors
  "
  constants.sort
end

.mode(opts = {}) ⇒ Object

Read the ai_sandbox key as strict, permissive, or off.



22
23
24
25
26
27
# File 'lib/pwn/plugins/ai_sandbox.rb', line 22

public_class_method def self.mode(opts = {})
  raw = opts[:mode] || opts[:ai_sandbox]
  raw = env_mode if raw.nil?
  value = raw.to_s.downcase
  %w[strict permissive off].include?(value) ? value.to_sym : :off
end

.required_binsObject



17
18
19
# File 'lib/pwn/plugins/ai_sandbox.rb', line 17

public_class_method def self.required_bins
  []
end

.wrap_ruby(opts = {}) ⇒ Object

Intercept pwn_eval when ai_sandbox is not off; nil means in-process eval.



58
59
60
61
62
# File 'lib/pwn/plugins/ai_sandbox.rb', line 58

public_class_method def self.wrap_ruby(opts = {})
  return nil if mode(opts) == :off

  exec(opts.merge(kind: :ruby, payload: opts[:code] || opts[:payload]))
end

.wrap_shell(opts = {}) ⇒ Object

Intercept shell when ai_sandbox is not off; nil means the caller runs host Open3.



51
52
53
54
55
# File 'lib/pwn/plugins/ai_sandbox.rb', line 51

public_class_method def self.wrap_shell(opts = {})
  return nil if mode(opts) == :off

  exec(opts.merge(kind: :shell, payload: opts[:command] || opts[:payload]))
end