php-serialized-formatter 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/bin/psf +78 -0
- data/lib/psf/php_serial.rb +152 -0
- data/lib/psf/string_utils.rb +19 -0
- data/lib/psf/version.rb +6 -0
- data/lib/psf.rb +4 -0
- metadata +71 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: '059244a56e51ddb0792c2c037873f656019ea069dd6c8b3617e85595b4fb6a53'
|
4
|
+
data.tar.gz: ad4c47309502acb762b9cf8dfb804d3ac5040914aa8fb4d5cb141648a0ea5f64
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e5f968144bbf15c9cf8d0b3d3e546e86c4e76d4c51b9327f81e690d24aa5f683a06803de8a0dbef7437c98256c4561ada0ee62db6f204f6ad29c1902c45da2c6
|
7
|
+
data.tar.gz: be61df6b4acb9dbaf91a7aef59642aea213502f753e16b10a207d5a63392b03e290832175fcaa4910f42178e54ec5b0399e8def567a5a3ab17badd2bf720c3c1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 Alexandre ZANNI (noraj)
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/bin/psf
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'psf'
|
5
|
+
require 'docopt'
|
6
|
+
|
7
|
+
doc = <<~DOCOPT
|
8
|
+
php-serialized-formatter (psf) v#{Psf::VERSION}
|
9
|
+
|
10
|
+
Usage:
|
11
|
+
psf unserialize [--session] (--string <string> | --file <file> ) [--format <format>]
|
12
|
+
psf -h | --help
|
13
|
+
psf --version
|
14
|
+
|
15
|
+
Commands:
|
16
|
+
unserialize Unserialize PHP serialized objects
|
17
|
+
|
18
|
+
Options:
|
19
|
+
--session Session mode [default: false]
|
20
|
+
-s <string>, --string <string> Serialized content string, read from STDIN if equal to "-"
|
21
|
+
-f <file>, --file <file> Serialized content file
|
22
|
+
--format <format> Output format (hash, json, json_pretty, yaml) [default: hash]
|
23
|
+
--debug Display arguments
|
24
|
+
-h, --help Show this screen
|
25
|
+
--version Show version
|
26
|
+
|
27
|
+
Examples:
|
28
|
+
psf unserialize --session -f /var/lib/php/session/sess_3cebqoq314pcnc2jgqiu840h0k
|
29
|
+
psf unserialize -s 'O:6:"Person":2:{s:10:"first_name";s:4:"John";s:9:"last_name";s:3:"Doe";}' --format yaml
|
30
|
+
|
31
|
+
Project:
|
32
|
+
author (https://pwn.by/noraj / https://twitter.com/noraj_rawsec)
|
33
|
+
source (https://github.com/noraj/php-serialized-formatter)
|
34
|
+
documentation (https://noraj.github.io/php-serialized-formatter)
|
35
|
+
DOCOPT
|
36
|
+
|
37
|
+
begin
|
38
|
+
args = Docopt.docopt(doc, version: Psf::VERSION)
|
39
|
+
puts args if args['--debug']
|
40
|
+
# use case 1, using the tool
|
41
|
+
if args['unserialize']
|
42
|
+
# File or string ?
|
43
|
+
serialized_data = ''
|
44
|
+
if args['--string']
|
45
|
+
args['--string'] = $stdin.read.chomp if args['--string'] == '-'
|
46
|
+
serialized_data = args['--string']
|
47
|
+
elsif args['--file']
|
48
|
+
serialized_data = File.read(args['--file'])
|
49
|
+
end
|
50
|
+
# Session or regular object ?
|
51
|
+
unserialized_data = if args['--session']
|
52
|
+
Psf.unserialize_session(serialized_data)
|
53
|
+
else
|
54
|
+
Psf.unserialize(serialized_data)
|
55
|
+
end
|
56
|
+
# Format ?
|
57
|
+
case args['--format']
|
58
|
+
when 'hash'
|
59
|
+
require 'pp'
|
60
|
+
pp unserialized_data
|
61
|
+
when 'json'
|
62
|
+
require 'json'
|
63
|
+
puts unserialized_data.to_json
|
64
|
+
when 'json_pretty'
|
65
|
+
require 'json'
|
66
|
+
puts JSON.pretty_generate(unserialized_data)
|
67
|
+
when 'yaml'
|
68
|
+
require 'yaml'
|
69
|
+
puts unserialized_data.to_yaml
|
70
|
+
else
|
71
|
+
raise 'Unexisting format specified'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# use case 2, help: already handled by docopt
|
75
|
+
# use case 3, version: already handled by docopt
|
76
|
+
rescue Docopt::Exit => e
|
77
|
+
puts e.message
|
78
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# frozen_string_literal: false
|
2
|
+
|
3
|
+
require 'psf/string_utils'
|
4
|
+
|
5
|
+
# PHP serialization library that can parse and generate PHP's serialization
|
6
|
+
# format including PHP sessions specific format.
|
7
|
+
#
|
8
|
+
# Thos module is a patched, modified and enhanced version of
|
9
|
+
# [jurias/php-serial](https://github.com/jurias/php-serial/blob/master/lib/php-serial.rb)
|
10
|
+
# (under [MIT license](https://github.com/jurias/php-serial/blob/master/LICENSE)).
|
11
|
+
module Psf
|
12
|
+
# Serializes a ruby object into PHP serialized format.
|
13
|
+
# @param var [NilClass|Fixnum|Float|TrueClass|FalseClass|String|Array|Hash|Object]
|
14
|
+
# @return [String]
|
15
|
+
def self.serialize(var)
|
16
|
+
val = ''
|
17
|
+
case var.class.to_s
|
18
|
+
when 'NilClass'
|
19
|
+
val = 'N;'
|
20
|
+
when 'Integer', 'Fixnum', 'Bignum'
|
21
|
+
val = "i:#{var};"
|
22
|
+
when 'Float'
|
23
|
+
val = "d:#{var};"
|
24
|
+
when 'TrueClass'
|
25
|
+
val = 'b:1;'
|
26
|
+
when 'FalseClass'
|
27
|
+
val = 'b:0;'
|
28
|
+
when 'String', 'Symbol'
|
29
|
+
val = "s:#{var.to_s.bytesize}:\"#{var}\";"
|
30
|
+
when 'Array'
|
31
|
+
val = "a:#{var.length}:{"
|
32
|
+
var.length.times do |index|
|
33
|
+
val += serialize(index) + serialize(var[index])
|
34
|
+
end
|
35
|
+
val += '}'
|
36
|
+
when 'Hash'
|
37
|
+
val = "a:#{var.length}:{"
|
38
|
+
var.each do |item_key, item_value|
|
39
|
+
val += serialize(item_key) + serialize(item_value)
|
40
|
+
end
|
41
|
+
val += '}'
|
42
|
+
else
|
43
|
+
klass = var.class.to_s
|
44
|
+
val = "O:#{klass.length}:\"#{klass}\":#{var.instance_variables.length}:{"
|
45
|
+
var.instance_variables.each do |ivar|
|
46
|
+
ivar = ivar.to_s
|
47
|
+
ivar.slice!(0)
|
48
|
+
val += serialize(ivar) + serialize(var.send(ivar))
|
49
|
+
end
|
50
|
+
val += '}'
|
51
|
+
end
|
52
|
+
val
|
53
|
+
end
|
54
|
+
|
55
|
+
# Serializes a hash into PHP session.
|
56
|
+
# @param hash [Hash]
|
57
|
+
# @return [String]
|
58
|
+
def self.serialize_session(hash)
|
59
|
+
serialized_session = ''
|
60
|
+
hash.each do |key, value|
|
61
|
+
serialized_session += "#{key}|#{serialize(value)}"
|
62
|
+
end
|
63
|
+
serialized_session
|
64
|
+
end
|
65
|
+
|
66
|
+
# Unserializes a PHP session.
|
67
|
+
# @param data [String]
|
68
|
+
# @return [Hash]
|
69
|
+
def self.unserialize_session(data)
|
70
|
+
begin
|
71
|
+
data = data.strip
|
72
|
+
rescue Encoding::CompatibilityError
|
73
|
+
data = data.encode('UTF-8', invalid: :replace, undef: :replace).strip if data.encoding == Encoding::UTF_8
|
74
|
+
end
|
75
|
+
hash = {}
|
76
|
+
|
77
|
+
until data.empty?
|
78
|
+
key = extract_until!(data, '|')
|
79
|
+
hash[key] = unserialize!(data)
|
80
|
+
end
|
81
|
+
hash
|
82
|
+
end
|
83
|
+
|
84
|
+
# Unserializes a string up to the first valid serialized instance.
|
85
|
+
# @param data [String]
|
86
|
+
# @return [NilClass|Fixnum|Float|TrueClass|FalseClass|String|Array|Hash|Object]
|
87
|
+
def self.unserialize(data = '')
|
88
|
+
unserialize!(data.strip)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Unserializes recursively. Consumes the input string.
|
92
|
+
# @param data [String]
|
93
|
+
# @return [NilClass|Fixnum|Float|TrueClass|FalseClass|String|Array|Hash|Object]
|
94
|
+
def self.unserialize!(data = '')
|
95
|
+
var_type = data.slice!(0)
|
96
|
+
data.slice!(0)
|
97
|
+
case var_type
|
98
|
+
when 'N'
|
99
|
+
value = nil
|
100
|
+
when 'b'
|
101
|
+
value = (extract_until!(data, ';') == '1')
|
102
|
+
when 's'
|
103
|
+
length = extract_until!(data, ':').to_i
|
104
|
+
extract_until!(data, '"')
|
105
|
+
value = data.byteslice!(0, length)
|
106
|
+
extract_until!(data, ';')
|
107
|
+
when 'i'
|
108
|
+
value = extract_until!(data, ';').to_i
|
109
|
+
when 'd'
|
110
|
+
value = extract_until!(data, ';').to_f
|
111
|
+
when 'a'
|
112
|
+
value = {}
|
113
|
+
length = extract_until!(data, ':').to_i
|
114
|
+
extract_until!(data, '{')
|
115
|
+
length.times do
|
116
|
+
key = unserialize!(data)
|
117
|
+
value[key] = unserialize!(data)
|
118
|
+
end
|
119
|
+
extract_until!(data, '}')
|
120
|
+
# if keys are sequential numbers, return array
|
121
|
+
value = value.values if (Array(0..value.length - 1) == value.keys) && !value.empty?
|
122
|
+
when 'O'
|
123
|
+
value = {}
|
124
|
+
length = extract_until!(data, ':').to_i
|
125
|
+
extract_until!(data, '"')
|
126
|
+
value['class'] = data.slice!(0, length)
|
127
|
+
extract_until!(data, ':')
|
128
|
+
length = extract_until!(data, ':').to_i
|
129
|
+
extract_until!(data, '{')
|
130
|
+
length.times do
|
131
|
+
key = unserialize!(data)
|
132
|
+
value[key] = unserialize!(data)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
value
|
136
|
+
end
|
137
|
+
|
138
|
+
# Return all characters up to the first occurrence of char.
|
139
|
+
# Truncates those characters from input string.
|
140
|
+
# @param str [String]
|
141
|
+
# @param char [String]
|
142
|
+
# @return [String]
|
143
|
+
def self.extract_until!(str, char)
|
144
|
+
extracted = ''
|
145
|
+
while (c = str.slice!(0))
|
146
|
+
break if c == char
|
147
|
+
|
148
|
+
extracted << c
|
149
|
+
end
|
150
|
+
extracted
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Extending String class with new methods
|
4
|
+
class String
|
5
|
+
# In place version of String#byteslice(index, length)
|
6
|
+
def byteslice!(start_index, length = nil)
|
7
|
+
byte_start_index = start_index
|
8
|
+
byte_length = length || (bytesize - byte_start_index)
|
9
|
+
|
10
|
+
byte_start_index = bytesize + byte_start_index if byte_start_index.negative?
|
11
|
+
return nil if byte_start_index.negative? || byte_start_index >= bytesize
|
12
|
+
|
13
|
+
byte_length = bytesize - byte_start_index if byte_start_index + byte_length > bytesize
|
14
|
+
|
15
|
+
out = byteslice(byte_start_index, byte_length)
|
16
|
+
replace(byteslice(0, byte_start_index) + byteslice(byte_start_index + byte_length..-1))
|
17
|
+
out
|
18
|
+
end
|
19
|
+
end
|
data/lib/psf/version.rb
ADDED
data/lib/psf.rb
ADDED
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: php-serialized-formatter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexandre ZANNI
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-05-04 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: docopt
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.6'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.6'
|
26
|
+
description: PHP serialization library that can parse and generate PHP'sserialization
|
27
|
+
format including PHP sessions specific format.
|
28
|
+
email: alexandre.zanni@europe.com
|
29
|
+
executables:
|
30
|
+
- psf
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- LICENSE
|
35
|
+
- bin/psf
|
36
|
+
- lib/psf.rb
|
37
|
+
- lib/psf/php_serial.rb
|
38
|
+
- lib/psf/string_utils.rb
|
39
|
+
- lib/psf/version.rb
|
40
|
+
homepage: https://github.com/noraj/php-serialized-formatter
|
41
|
+
licenses:
|
42
|
+
- MIT
|
43
|
+
metadata:
|
44
|
+
yard.run: yard
|
45
|
+
bug_tracker_uri: https://github.com/noraj/php-serialized-formatter/issues
|
46
|
+
changelog_uri: https://github.com/noraj/php-serialized-formatter/releases
|
47
|
+
documentation_uri: https://noraj.github.io/php-serialized-formatter/
|
48
|
+
homepage_uri: https://github.com/noraj/php-serialized-formatter
|
49
|
+
source_code_uri: https://github.com/noraj/php-serialized-formatter/
|
50
|
+
rubygems_mfa_required: 'true'
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
version: 3.1.0
|
59
|
+
- - "<"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '4.0'
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubygems_version: 3.6.2
|
69
|
+
specification_version: 4
|
70
|
+
summary: Serialize and unserialize to|from PHP session|objects.
|
71
|
+
test_files: []
|