Initial commit
This commit is contained in:
commit
0873cadc4c
0
.idea/.gitignore
generated
vendored
Normal file
0
.idea/.gitignore
generated
vendored
Normal file
6
.idea/discord.xml
generated
Normal file
6
.idea/discord.xml
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="DiscordProjectSettings">
|
||||||
|
<option name="show" value="PROJECT_FILES" />
|
||||||
|
</component>
|
||||||
|
</project>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/sshoneypot.iml" filepath="$PROJECT_DIR$/.idea/sshoneypot.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
110
.idea/workspace.xml
generated
Normal file
110
.idea/workspace.xml
generated
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="84062d71-ebe8-4cc4-8ed7-9375510de988" name="Default Changelist" comment="" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="FileTemplateManagerImpl">
|
||||||
|
<option name="RECENT_TEMPLATES">
|
||||||
|
<list>
|
||||||
|
<option value="Go Application" />
|
||||||
|
<option value="Go File" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="GOROOT" url="file:///usr/lib/go" />
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="GitSEFilterConfiguration">
|
||||||
|
<file-type-list>
|
||||||
|
<filtered-out-file-type name="LOCAL_BRANCH" />
|
||||||
|
<filtered-out-file-type name="REMOTE_BRANCH" />
|
||||||
|
<filtered-out-file-type name="TAG" />
|
||||||
|
<filtered-out-file-type name="COMMIT_BY_MESSAGE" />
|
||||||
|
</file-type-list>
|
||||||
|
</component>
|
||||||
|
<component name="GoLibraries">
|
||||||
|
<option name="indexEntireGoPath" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectId" id="1pZZ5aRCduBrvaNLZvRCKVbHVxi" />
|
||||||
|
<component name="ProjectLevelVcsManager" settingsEditedManually="true" />
|
||||||
|
<component name="ProjectViewState">
|
||||||
|
<option name="hideEmptyMiddlePackages" value="true" />
|
||||||
|
<option name="showLibraryContents" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="DefaultGoTemplateProperty" value="Go File" />
|
||||||
|
<property name="RunOnceActivity.OpenProjectViewOnStart" value="true" />
|
||||||
|
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="dart.analysis.tool.window.visible" value="false" />
|
||||||
|
<property name="go.import.settings.migrated" value="true" />
|
||||||
|
<property name="go.sdk.automatically.set" value="true" />
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
<property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
|
||||||
|
</component>
|
||||||
|
<component name="RecentsManager">
|
||||||
|
<key name="MoveFile.RECENT_KEYS">
|
||||||
|
<recent name="$PROJECT_DIR$/sshoneypot/info" />
|
||||||
|
<recent name="$PROJECT_DIR$/sshoneypot" />
|
||||||
|
<recent name="$PROJECT_DIR$" />
|
||||||
|
</key>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Go Build.go build github.com/bytedream/sshoneypot">
|
||||||
|
<configuration name="go build github.com/bytedream/sshoneypot" type="GoApplicationRunConfiguration" factoryName="Go Application" temporary="true" nameIsGenerated="true">
|
||||||
|
<module name="sshoneypot" />
|
||||||
|
<working_directory value="$PROJECT_DIR$" />
|
||||||
|
<kind value="PACKAGE" />
|
||||||
|
<package value="github.com/bytedream/sshoneypot" />
|
||||||
|
<directory value="$PROJECT_DIR$" />
|
||||||
|
<filePath value="$PROJECT_DIR$/main.go" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<configuration name="dump_fs" type="PythonConfigurationType" factoryName="Python" temporary="true" nameIsGenerated="true">
|
||||||
|
<module name="sshoneypot" />
|
||||||
|
<option name="INTERPRETER_OPTIONS" value="" />
|
||||||
|
<option name="PARENT_ENVS" value="true" />
|
||||||
|
<envs>
|
||||||
|
<env name="PYTHONUNBUFFERED" value="1" />
|
||||||
|
</envs>
|
||||||
|
<option name="SDK_HOME" value="" />
|
||||||
|
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$" />
|
||||||
|
<option name="IS_MODULE_SDK" value="true" />
|
||||||
|
<option name="ADD_CONTENT_ROOTS" value="true" />
|
||||||
|
<option name="ADD_SOURCE_ROOTS" value="true" />
|
||||||
|
<option name="SCRIPT_NAME" value="$PROJECT_DIR$/dump_fs.py" />
|
||||||
|
<option name="PARAMETERS" value="" />
|
||||||
|
<option name="SHOW_COMMAND_LINE" value="false" />
|
||||||
|
<option name="EMULATE_TERMINAL" value="false" />
|
||||||
|
<option name="MODULE_MODE" value="false" />
|
||||||
|
<option name="REDIRECT_INPUT" value="false" />
|
||||||
|
<option name="INPUT_FILE" value="" />
|
||||||
|
<method v="2" />
|
||||||
|
</configuration>
|
||||||
|
<recent_temporary>
|
||||||
|
<list>
|
||||||
|
<item itemvalue="Go Build.go build github.com/bytedream/sshoneypot" />
|
||||||
|
</list>
|
||||||
|
</recent_temporary>
|
||||||
|
</component>
|
||||||
|
<component name="SpellCheckerSettings" RuntimeDictionaries="0" Folders="0" CustomDictionaries="0" DefaultDictionary="application-level" UseSingleDictionary="true" transferred="true" />
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
<component name="Vcs.Log.Tabs.Properties">
|
||||||
|
<option name="TAB_STATES">
|
||||||
|
<map>
|
||||||
|
<entry key="MAIN">
|
||||||
|
<value>
|
||||||
|
<State />
|
||||||
|
</value>
|
||||||
|
</entry>
|
||||||
|
</map>
|
||||||
|
</option>
|
||||||
|
<option name="oldMeFiltersMigrated" value="true" />
|
||||||
|
</component>
|
||||||
|
</project>
|
143
README.md
Normal file
143
README.md
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
# ⚠️ UNFINISHED PROJECT ⚠️
|
||||||
|
|
||||||
|
> A try to write a own ssh honeypot. Higly inspirated by [sshesame](https://github.com/jaksi/sshesame).
|
||||||
|
|
||||||
|
# sshoneypot
|
||||||
|
|
||||||
|
Go 1.10
|
||||||
|
|
||||||
|
**sshoneypot** easy is a fake ssh server that lets everyone connect, logs their activity and can be implemented easily in your project, or can be used as a standalone application.
|
||||||
|
The ssh server has a emulated, full functional linux filesystem. For more details about the filesystem see [here](info/fs.go).
|
||||||
|
It also contains some basic linux commands like `cd`, `ls` or `stat`. You can add commands by yourself too, see [here](#own-commands) how.
|
||||||
|
|
||||||
|
The project itself is just a library, but you can run it standalone via [docker](#Docker).
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
## Own commands
|
||||||
|
|
||||||
|
If the standard commands aren't enough, you can easily implement you owns
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Warning
|
||||||
|
This software, just like any other, might contain bugs. Given the popular nature of SSH, you probably shouldn't run it unsupervised as root on a production server on port 22. Use common sense.
|
||||||
|
|
||||||
|
## Motivation
|
||||||
|
I was just curious what all these guys were up to:
|
||||||
|
```
|
||||||
|
sshd[8128]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=<client> user=root
|
||||||
|
sshd[8128]: Failed password for root from <client> port 37510 ssh2
|
||||||
|
sshd[8128]: Received disconnect from <client> port 37510:11: [preauth]
|
||||||
|
sshd[8128]: Disconnected from <client> port 37510 [preauth]
|
||||||
|
sshd[8141]: Received disconnect from <client> port 59353:11: [preauth]
|
||||||
|
sshd[8141]: Disconnected from <client> port 59353 [preauth]
|
||||||
|
sshd[8151]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=<client> user=root
|
||||||
|
sshd[8151]: Failed password for root from <client> port 63785 ssh2
|
||||||
|
sshd[8159]: Received disconnect from <client> port 24889:11: [preauth]
|
||||||
|
sshd[8159]: Disconnected from <client> port 24889 [preauth]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Details
|
||||||
|
`sshesame` accepts and logs
|
||||||
|
* every password authentication request,
|
||||||
|
* every SSH channel open request and
|
||||||
|
* every SSH request
|
||||||
|
|
||||||
|
**without actually executing anything on the host**.
|
||||||
|
|
||||||
|
For more details, read the [relevant RFC](https://tools.ietf.org/html/rfc4254).
|
||||||
|
|
||||||
|
## Installing
|
||||||
|
### From source
|
||||||
|
* [Install go](https://golang.org/doc/install) (version 1.4 or newer required)
|
||||||
|
* `go get -u github.com/jaksi/sshesame`
|
||||||
|
|
||||||
|
### Snap
|
||||||
|
`snap install sshesame`
|
||||||
|
|
||||||
|
Package created and maintained by [chadmiller](https://github.com/chadmiller).
|
||||||
|
|
||||||
|
You can find the package [here](https://code.launchpad.net/~privacy-squad/+junk/sshesame-snap).
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("aa")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
```
|
||||||
|
$ sshesame -h
|
||||||
|
Usage of sshesame:
|
||||||
|
-host_key string
|
||||||
|
a file containing a private key to use
|
||||||
|
-json_logging
|
||||||
|
enable logging in JSON
|
||||||
|
-listen_address string
|
||||||
|
the local address to listen on (default "localhost")
|
||||||
|
-port uint
|
||||||
|
the port number to listen on (default 2022)
|
||||||
|
-server_version string
|
||||||
|
The version identification of the server (RFC 4253 section 4.2 requires that this string start with "SSH-2.0-") (default "SSH-2.0-sshesame")
|
||||||
|
```
|
||||||
|
Consider creating a private key to use with sshesame, for example using `ssh-keygen`.
|
||||||
|
|
||||||
|
## Example output
|
||||||
|
```
|
||||||
|
Connection: client=<client>:45782
|
||||||
|
Login: client=<client>:45782, user="root", password="cisco"
|
||||||
|
Established SSH connection: client=<client>:45782
|
||||||
|
New channel: clinet=<client>:45782, type=direct-tcpip, payload={DestinationAddress:<something> DestinationPort:110 SourceAddress:192.168.0.1 SourcePort:0}
|
||||||
|
Failed to read from channel: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=direct-tcpip, payload={DestinationAddress:<something> DestinationPort:143 SourceAddress:192.168.0.1 SourcePort:0}
|
||||||
|
Failed to read from channel: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=direct-tcpip, payload={DestinationAddress:<something> DestinationPort:587 SourceAddress:192.168.0.1 SourcePort:0}
|
||||||
|
Failed to read from channel: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=direct-tcpip, payload={DestinationAddress:<something> DestinationPort:587 SourceAddress:192.168.0.1 SourcePort:0}
|
||||||
|
Failed to read from channel: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=session, payload=[]
|
||||||
|
Request: client=<client>:45782, channel=session, type=exec, payload={Command:/sbin/ifconfig}
|
||||||
|
Failed to read from terminal: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=session, payload=[]
|
||||||
|
Request: client=<client>:45782, channel=session, type=exec, payload={Command:cat /proc/meminfo}
|
||||||
|
Failed to read from terminal: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=session, payload=[]
|
||||||
|
Request: client=<client>:45782, channel=session, type=exec, payload={Command:2>/dev/null sh -c 'cat /lib/libdl.so* || cat /lib/librt.so* || cat /bin/cat || cat /sbin/ifconfig'}
|
||||||
|
Failed to read from terminal: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=session, payload=[]
|
||||||
|
Request: client=<client>:45782, channel=session, type=exec, payload={Command:cat /proc/version}
|
||||||
|
Failed to read from terminal: EOF
|
||||||
|
New channel: clinet=<client>:45782, type=session, payload=[]
|
||||||
|
Request: client=<client>:45782, channel=session, type=exec, payload={Command:uptime}
|
||||||
|
Failed to read from terminal: EOF
|
||||||
|
Disconnect: client=<client>:45782
|
||||||
|
```
|
||||||
|
So what happened here?
|
||||||
|
* A client logged in with the user "root" and the password "cisco"
|
||||||
|
* Using TCP/IP forwarding over SSH, they tried to connect to a few remote mail servers over POP3 (port 110), IMAP (port 143) and Submission (port 587)
|
||||||
|
* They tried to execute a few commands to get some information about the host
|
||||||
|
|
||||||
|
Again, if you're interested in the technical details of SSH, read the [RFC](https://tools.ietf.org/html/rfc4254).
|
||||||
|
|
||||||
|
## Inspired
|
||||||
|
|
||||||
|
This project was inspired from some the following projects
|
||||||
|
|
||||||
|
- [sshesame](https://github.com/jaksi/sshesame) (another go based fake ssh server)
|
||||||
|
|
||||||
|
## Implementation
|
63
dump_fs.py
Normal file
63
dump_fs.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
excluded = ('/tmp', '/proc')
|
||||||
|
|
||||||
|
|
||||||
|
def info(file: str) -> list:
|
||||||
|
try:
|
||||||
|
stat = os.stat(file)
|
||||||
|
# this error may occur when temporary files are being passed as argument and deleted before `os.stat(...)` can read it
|
||||||
|
except FileNotFoundError:
|
||||||
|
import stat
|
||||||
|
stat.S_ISBLK()
|
||||||
|
return None
|
||||||
|
# occurs when you do not have the rights to read the given file
|
||||||
|
except PermissionError:
|
||||||
|
sys.stderr.write(f"Permission denied: '{file}'\n")
|
||||||
|
return None
|
||||||
|
|
||||||
|
type = int(oct(stat.st_mode)[2:-4])
|
||||||
|
|
||||||
|
size = stat.st_size
|
||||||
|
links = stat.st_nlink
|
||||||
|
permissions = int(oct(stat.st_mode)[-4:], 8)
|
||||||
|
|
||||||
|
creation_timestamp = int(stat.st_ctime)
|
||||||
|
# access_timestamp = stat.st_atime
|
||||||
|
# modification_timestamp = stat.st_mtime
|
||||||
|
|
||||||
|
user_id = stat.st_uid if stat.st_uid in [0, 1000] else 1000
|
||||||
|
group_id = stat.st_gid if stat.st_gid in [0, 1000] else 1000
|
||||||
|
|
||||||
|
return [type, size, links, permissions, creation_timestamp, user_id, group_id]
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
fs = {'files': {}, 'info': info('/')}
|
||||||
|
for root, dirs, files in os.walk('/'):
|
||||||
|
|
||||||
|
# checks if the path is in the `excluded` list and if so, it skips this iteration
|
||||||
|
if root.startswith(excluded):
|
||||||
|
continue
|
||||||
|
|
||||||
|
fs_root = fs['files']
|
||||||
|
for dir in root.split(os.sep):
|
||||||
|
if dir.startswith('/'):
|
||||||
|
dir = dir[1:]
|
||||||
|
if dir != "":
|
||||||
|
fs_root = fs_root[dir]['files']
|
||||||
|
|
||||||
|
for dir in dirs:
|
||||||
|
specs = info(os.path.join(root, dir))
|
||||||
|
fs_root[dir] = {'files': {}, 'info': specs}
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
specs = info(os.path.join(root, file))
|
||||||
|
if specs:
|
||||||
|
fs_root[file] = specs
|
||||||
|
|
||||||
|
json.dump(fs, open('fs.json', 'w+'))
|
41
main.go
Normal file
41
main.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"io/fs"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
//fmt.Println(strings.Split("/etc/aaa", string(os.PathSeparator))[1:])
|
||||||
|
|
||||||
|
/*fs, err := sshoneypot.LoadFSFromJson("fs.json")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if file, ok := fs.GetFile("/etc"); ok {
|
||||||
|
d, _ := file.(sshoneypot.Directory)
|
||||||
|
fmt.Println(d.Files)
|
||||||
|
//fmt.Println(file.(sshoneypot.Directory).Files)
|
||||||
|
}*/
|
||||||
|
|
||||||
|
var key ssh.Signer
|
||||||
|
if _, err := os.Stat("ssh.key"); os.IsNotExist(err) {
|
||||||
|
privateKey, _ := sshoneypot.GenerateSSHKey()
|
||||||
|
ioutil.WriteFile("ssh.key", privateKey, fs.ModePerm)
|
||||||
|
key, _ = sshoneypot.LoadSSHKey(privateKey)
|
||||||
|
} else {
|
||||||
|
key, _ = sshoneypot.LoadSSHKeyFromFile("ssh.key")
|
||||||
|
}
|
||||||
|
|
||||||
|
filesystem, _ := info.LoadFSFromJson("fs.json")
|
||||||
|
filesystem.Manipulate = true
|
||||||
|
sshServer := sshoneypot.DefaultSSHoneypot(filesystem, key)
|
||||||
|
if err := sshServer.Serve(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
27
ssh.key
Executable file
27
ssh.key
Executable file
@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEogIBAAKCAQEAwOnBoYINHWj92prwhQHzLv3pDHAdqui6R4Ap7aWccSEM8fh7
|
||||||
|
4xaqnDSCTPnkndec2t9wkR0rKNcharUSg9RUDyFBTcXeFlDEnEIHQW60iX6WnMAn
|
||||||
|
FUu2dqdX2SkXOvmERBkeQhukYQGrmTLY9KChgMUrLT698G12BiR3QdnwsfIu0ICO
|
||||||
|
PvICqYqn27J78UAArHveHzdxL2nOZhsEFaI5HawqZDc3BRy62yNO/u1Vx+FlbzuY
|
||||||
|
ToYSWYHvItzOFEkQudeJLXwBc6q1tZyLrmriLUek78MXIa3cZxeA6QbQkxPcVvG6
|
||||||
|
ALTgJ5kVS/ryr2VdrvCZs6qeQhxzVkmf+UnY7wIDAQABAoIBACSlAqwMSTN5+yao
|
||||||
|
YeHSIgCeKMO9FFWfyarFVLGY0OPIdG4OVnInnxb2/n1ixqOZDmmhIf/eu5ERdH6R
|
||||||
|
kMfL8H/DQGVmna49f2tzO2+ZeN/ZVQDHm2T3MdzOIujUXl5MgWxyHQZPu+TVbWQ3
|
||||||
|
fXDShnsweOgqT+g40r+N871licwzMUrEmfygYzvQtHmEnb0QS3r46kn45D/uPq/w
|
||||||
|
Cw29R7yibiaBLlGZdOHHMW4A9svWvJfOjTH5+6Rs/rINcWLhIqdV6XQoKYVizjnb
|
||||||
|
8u138/6Xenhq8+9gzJyTUfApBzN+t8lm+bwnuYH5EhGoNv8rPLk90dwHMbXJIkJA
|
||||||
|
pSQrSxkCgYEA0KL0Gz1ozdlLYfBtXfxQmg1tiehtEBM+2Zn9znvCVNG08r/MQu81
|
||||||
|
pyXIfhJWYYveFskLnG0XzS7Bj36V5SwONUtfUzw4ymLUJGknhRzMb/vQgWUcQrWO
|
||||||
|
wglgG8rQskkjzHR3gLIB4Hiv9o/9k8w0Bv/oa+lrQ1DInqPQGNtxIJsCgYEA7LUG
|
||||||
|
mpY5z9xxXF7GinqkFa0iVQ84zYwQ/eyHQfGiap80dgjTEnaEN2cLvl8aGBoXkwiV
|
||||||
|
Lcc/zbk0ftYPMXMwpYDXkeHY99IMwu0eicyOuckYB45n+q9ZsJPiEzkBwVRIdeEQ
|
||||||
|
PBSzFoPw1KXwe/T5xvPo6272lcF/wSIJLdo+fD0CgYAxGVg3HOGQKAX8e3dRefKB
|
||||||
|
/oz7um4ILW9KCFpZgHiAO4XI5ugsDF1lA5hGSwx3ElJmrFOGMYo7aDh3C4Q9FXwW
|
||||||
|
gLFjRjXbMxzXoMODKP7Xj7xG50OaU13QPiKXB8jLXDkHgZUp/TpB2EUY0lQLty+7
|
||||||
|
QfgxhRIQGm/MHaL4ZpQd5wKBgA9ArvdBHOKPAuL/3G1J787RxDeU7oUYNHQYTLLs
|
||||||
|
HhoeviOo9+jlqCllw3T17dmFvOUllW9IuozIFWDi7EG0eXsArWNiGTgG2fmpi+E3
|
||||||
|
RC8mjRzXiU23BOGC9ftlHf7WLoEiCojEkLGvuNILC08BfyLZzrV1BgCqYlAQoGTK
|
||||||
|
/mHFAoGAIVxgfCZsZ4+Nsik8T3eNstI5tytwyU4y27eQEpDvuzmYCMR0/F+6lj9z
|
||||||
|
nPBLMv5ELCQf98lotTLthKv2jRN45Xv9jZ+mu/4fRwdJXaEdc8vt72fX6HXU1Gzt
|
||||||
|
ZXGzkt+hJufUxLN6u3xgGqcRWBxs/xKvAcSEAkkqKy8RgCnNqXk=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
112
sshoneypot/handle.go
Normal file
112
sshoneypot/handle.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
package sshoneypot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
type payloadWindowChange struct {
|
||||||
|
Width, Height uint32
|
||||||
|
|
||||||
|
// always 0
|
||||||
|
PixelWidth, PixelHeight uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type payloadPty struct {
|
||||||
|
Term string
|
||||||
|
Width, Height uint32
|
||||||
|
|
||||||
|
// always 0
|
||||||
|
PixelWidth, PixelHeight uint32
|
||||||
|
|
||||||
|
Modes []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func handleRequest(requests <-chan *ssh.Request, term *info.Termina) {
|
||||||
|
for request := range requests {
|
||||||
|
switch request.Type {
|
||||||
|
case "pty-req":
|
||||||
|
var pty payloadPty
|
||||||
|
ssh.Unmarshal(request.Payload, &pty)
|
||||||
|
|
||||||
|
term.SetSize(int(pty.Width), int(pty.Height))
|
||||||
|
case "window-change":
|
||||||
|
var windowChange payloadWindowChange
|
||||||
|
ssh.Unmarshal(request.Payload, &windowChange)
|
||||||
|
|
||||||
|
term.SetSize(int(windowChange.Width), int(windowChange.Height))
|
||||||
|
}
|
||||||
|
// sends a reply if wanted
|
||||||
|
if request.WantReply {
|
||||||
|
request.Reply(true, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCommand(command string) ([]string, error) {
|
||||||
|
var args []string
|
||||||
|
state := "start"
|
||||||
|
current := ""
|
||||||
|
quote := "\""
|
||||||
|
escapeNext := true
|
||||||
|
for i := 0; i < len(command); i++ {
|
||||||
|
c := command[i]
|
||||||
|
|
||||||
|
if state == "quotes" {
|
||||||
|
if string(c) != quote {
|
||||||
|
current += string(c)
|
||||||
|
} else {
|
||||||
|
args = append(args, current)
|
||||||
|
current = ""
|
||||||
|
state = "start"
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if escapeNext {
|
||||||
|
current += string(c)
|
||||||
|
escapeNext = false
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
switch c {
|
||||||
|
case '\\':
|
||||||
|
escapeNext = true
|
||||||
|
continue
|
||||||
|
case '"', '\'':
|
||||||
|
state = "quotes"
|
||||||
|
quote = string(c)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == "arg" {
|
||||||
|
if c == ' ' || c == '\t' {
|
||||||
|
args = append(args, current)
|
||||||
|
current = ""
|
||||||
|
state = "start"
|
||||||
|
} else {
|
||||||
|
current += string(c)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if c != ' ' && c != '\t' {
|
||||||
|
state = "arg"
|
||||||
|
current += string(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if state == "quotes" {
|
||||||
|
return []string{}, errors.New(fmt.Sprintf("Unclosed quote in command line: %s", command))
|
||||||
|
}
|
||||||
|
|
||||||
|
if current != "" {
|
||||||
|
args = append(args, current)
|
||||||
|
}
|
||||||
|
|
||||||
|
return args, nil
|
||||||
|
}
|
538
sshoneypot/handler/fs.go
Normal file
538
sshoneypot/handler/fs.go
Normal file
@ -0,0 +1,538 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func basicFileCommandParser(info *info.Info, help string, availableOptions... string) (options []string, files []string, ok bool) {
|
||||||
|
if len(info.Args) == 0 {
|
||||||
|
info.Errorln(missingOperand(info))
|
||||||
|
return nil, nil, false
|
||||||
|
} else {
|
||||||
|
for _, arg := range info.Args {
|
||||||
|
if strings.HasPrefix(arg, "-") {
|
||||||
|
hasArg := len(availableOptions) == 0
|
||||||
|
|
||||||
|
if arg == "--help" {
|
||||||
|
info.Writeln(help)
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
for _, option := range availableOptions {
|
||||||
|
if arg == option {
|
||||||
|
hasArg = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options = append(options, arg)
|
||||||
|
|
||||||
|
if !hasArg{
|
||||||
|
info.Errorlnf("%s: unrecognized option '%s'", info.Command, arg)
|
||||||
|
info.Errorlnf("Try '%s --help' for more information.", info.Command)
|
||||||
|
return nil, nil, false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
files = append(files, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return options, files, true
|
||||||
|
}
|
||||||
|
|
||||||
|
type Cat struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cat Cat) Handle(cmdInfo *info.Info) {
|
||||||
|
if multipleArgs(cmdInfo) {
|
||||||
|
var paths []string
|
||||||
|
for _, arg := range cmdInfo.Args {
|
||||||
|
if strings.HasPrefix(arg, "-") {
|
||||||
|
switch arg {
|
||||||
|
case "--help":
|
||||||
|
cmdInfo.Writeln(cat.help())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
paths = append(paths, arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, path := range paths {
|
||||||
|
if file, ok := cmdInfo.FS.GetFile(path, false); ok {
|
||||||
|
if file.IsDir() {
|
||||||
|
cmdInfo.Writelnf("%s:%s: Is a directory", cmdInfo.Command, path)
|
||||||
|
} else {
|
||||||
|
realFile := file.(info.File)
|
||||||
|
bytes := make([]byte, realFile.Size)
|
||||||
|
// this will make the file content always the same
|
||||||
|
rand.Seed(realFile.Size + realFile.CreationTimestamp)
|
||||||
|
|
||||||
|
rand.Read(bytes)
|
||||||
|
cmdInfo.Writeln(string(bytes))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmdInfo.Writeln(noSuchFileOrDirectory(path, cmdInfo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cat Cat) help() string {
|
||||||
|
return `
|
||||||
|
Usage: cat [OPTION]... [FILE]...
|
||||||
|
Concatenate FILE(s) to standard output.
|
||||||
|
|
||||||
|
With no FILE, or when FILE is -, read standard input.
|
||||||
|
|
||||||
|
-A, --show-all equivalent to -vET
|
||||||
|
-b, --number-nonblank number nonempty output lines, overrides -n
|
||||||
|
-e equivalent to -vE
|
||||||
|
-E, --show-ends display $ at end of each line
|
||||||
|
-n, --number number all output lines
|
||||||
|
-s, --squeeze-blank suppress repeated empty output lines
|
||||||
|
-t equivalent to -vT
|
||||||
|
-T, --show-tabs display TAB characters as ^I
|
||||||
|
-u (ignored)
|
||||||
|
-v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
cat f - g Output f's contents, then standard input, then g's contents.
|
||||||
|
cat Copy standard input to standard output.
|
||||||
|
|
||||||
|
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
|
||||||
|
Report cat translation bugs to <https://translationproject.org/team/>
|
||||||
|
Full documentation at: <https://www.gnu.org/software/coreutils/cat>
|
||||||
|
or available locally via: info '(coreutils) cat invocation'`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CD struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd CD) Handle(cmdInfo *info.Info) {
|
||||||
|
if _, files, ok := basicFileCommandParser(cmdInfo, cd.help()); ok {
|
||||||
|
if len(files) != 1 {
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
path := files[0]
|
||||||
|
if _, ok := cmdInfo.FS.GetExplicitDirectory(path, true); ok {
|
||||||
|
if cmdInfo.Terminal != nil {
|
||||||
|
cmdInfo.Terminal.SetPrompt(fmt.Sprintf("%s:%s $ ", cmdInfo.User.User(), cmdInfo.FS.Cwd().Name))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmdInfo.Writelnf("-bash: cd: %s: No such file or directory", path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cd CD) help() string {
|
||||||
|
return `cd: cd [-L|[-P [-e]] [-@]] [dir]
|
||||||
|
Change the shell working directory.
|
||||||
|
|
||||||
|
Change the current directory to DIR. The default DIR is the value of the
|
||||||
|
HOME shell variable.
|
||||||
|
|
||||||
|
The variable CDPATH defines the search path for the directory containing
|
||||||
|
DIR. Alternative directory names in CDPATH are separated by a colon (:).
|
||||||
|
A null directory name is the same as the current directory. If DIR begins
|
||||||
|
with a slash (/), then CDPATH is not used.
|
||||||
|
|
||||||
|
If the directory is not found, and the shell option 'cdable_vars' is set,
|
||||||
|
the word is assumed to be a variable name. If that variable has a value,
|
||||||
|
its value is used for DIR.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-L force symbolic links to be followed: resolve symbolic
|
||||||
|
links in DIR after processing instances of '..'
|
||||||
|
-P use the physical directory structure without following
|
||||||
|
symbolic links: resolve symbolic links in DIR before
|
||||||
|
processing instances of '..'
|
||||||
|
-e if the -P option is supplied, and the current working
|
||||||
|
directory cannot be determined successfully, exit with
|
||||||
|
a non-zero status
|
||||||
|
-@ on systems that support it, present a file with extended
|
||||||
|
attributes as a directory containing the file attributes
|
||||||
|
|
||||||
|
The default is to follow symbolic links, as if '-L' were specified.
|
||||||
|
'..' is processed by removing the immediately previous pathname component
|
||||||
|
back to a slash or the beginning of DIR.
|
||||||
|
|
||||||
|
Exit Status:
|
||||||
|
Returns 0 if the directory is changed, and if $PWD is set successfully when
|
||||||
|
-P is used; non-zero otherwise.`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LS struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls LS) Handle(cmdInfo *info.Info) {
|
||||||
|
if len(cmdInfo.Args) == 0 {
|
||||||
|
cmdInfo.Args = []string{""}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, files, ok := basicFileCommandParser(cmdInfo, ls.help()); ok {
|
||||||
|
var errors []string
|
||||||
|
dirs := map[string]string{}
|
||||||
|
|
||||||
|
for _, dir := range files {
|
||||||
|
f, ok := cmdInfo.FS.GetFile(dir, false)
|
||||||
|
if !ok {
|
||||||
|
errors = append(errors, fmt.Sprintf("ls: cannot access '%s': No such file or directory", dir))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
switch f.(type) {
|
||||||
|
case info.Directory:
|
||||||
|
var length int
|
||||||
|
var files []string
|
||||||
|
for _, file := range f.(info.Directory).Files {
|
||||||
|
if l := len(file.Name); length < l {
|
||||||
|
length = l
|
||||||
|
}
|
||||||
|
files = append(files, file.Name)
|
||||||
|
}
|
||||||
|
length++
|
||||||
|
|
||||||
|
sort.Strings(files)
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
var perline int
|
||||||
|
if cmdInfo.Terminal.Width > 0 {
|
||||||
|
perline = int(cmdInfo.Terminal.Width) / length
|
||||||
|
} else {
|
||||||
|
perline = 80 / length
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, file := range files {
|
||||||
|
if i % perline == 0 && i != 0 {
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
builder.WriteString(file + strings.Repeat(" ", length - len(file)))
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs[dir] = builder.String()
|
||||||
|
case info.File:
|
||||||
|
dirs[dir] = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dirs) == 1 && len(errors) == 0 {
|
||||||
|
for _, v := range dirs {
|
||||||
|
cmdInfo.Writeln(v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var builder strings.Builder
|
||||||
|
for _, err := range errors {
|
||||||
|
builder.WriteString(err)
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
if len(errors) > 0 && len(dirs) > 0 {
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
for k, v := range dirs {
|
||||||
|
builder.WriteString(fmt.Sprintf("%s:\n", k))
|
||||||
|
builder.WriteString(v)
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
cmdInfo.Write(builder.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ls LS) help() string {
|
||||||
|
return `Usage: ls [OPTION]... [FILE]...
|
||||||
|
List information about the FILEs (the current directory by default).
|
||||||
|
Sort entries alphabetically if none of -cftuvSUX nor --sort is specified.
|
||||||
|
|
||||||
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
-a, --all do not ignore entries starting with .
|
||||||
|
-A, --almost-all do not list implied . and ..
|
||||||
|
--author with -l, print the author of each file
|
||||||
|
-b, --escape print C-style escapes for nongraphic characters
|
||||||
|
--block-size=SIZE with -l, scale sizes by SIZE when printing them;
|
||||||
|
e.g., '--block-size=M'; see SIZE format below
|
||||||
|
-B, --ignore-backups do not list implied entries ending with ~
|
||||||
|
-c with -lt: sort by, and show, ctime (time of last
|
||||||
|
modification of file status information);
|
||||||
|
with -l: show ctime and sort by name;
|
||||||
|
otherwise: sort by ctime, newest first
|
||||||
|
-C list entries by columns
|
||||||
|
--color[=WHEN] colorize the output; WHEN can be 'always' (default
|
||||||
|
if omitted), 'auto', or 'never'; more info below
|
||||||
|
-d, --directory list directories themselves, not their contents
|
||||||
|
-D, --dired generate output designed for Emacs' dired mode
|
||||||
|
-f do not sort, enable -aU, disable -ls --color
|
||||||
|
-F, --classify append indicator (one of */=>@|) to entries
|
||||||
|
--file-type likewise, except do not append '*'
|
||||||
|
--format=WORD across -x, commas -m, horizontal -x, long -l,
|
||||||
|
single-column -1, verbose -l, vertical -C
|
||||||
|
--full-time like -l --time-style=full-iso
|
||||||
|
-g like -l, but do not list owner
|
||||||
|
--group-directories-first
|
||||||
|
group directories before files;
|
||||||
|
can be augmented with a --sort option, but any
|
||||||
|
use of --sort=none (-U) disables grouping
|
||||||
|
-G, --no-group in a long listing, don't print group names
|
||||||
|
-h, --human-readable with -l and -s, print sizes like 1K 234M 2G etc.
|
||||||
|
--si likewise, but use powers of 1000 not 1024
|
||||||
|
-H, --dereference-command-line
|
||||||
|
follow symbolic links listed on the command line
|
||||||
|
--dereference-command-line-symlink-to-dir
|
||||||
|
follow each command line symbolic link
|
||||||
|
that points to a directory
|
||||||
|
--hide=PATTERN do not list implied entries matching shell PATTERN
|
||||||
|
(overridden by -a or -A)
|
||||||
|
--hyperlink[=WHEN] hyperlink file names; WHEN can be 'always'
|
||||||
|
(default if omitted), 'auto', or 'never'
|
||||||
|
--indicator-style=WORD append indicator with style WORD to entry names:
|
||||||
|
none (default), slash (-p),
|
||||||
|
file-type (--file-type), classify (-F)
|
||||||
|
-i, --inode print the index number of each file
|
||||||
|
-I, --ignore=PATTERN do not list implied entries matching shell PATTERN
|
||||||
|
-k, --kibibytes default to 1024-byte blocks for disk usage;
|
||||||
|
used only with -s and per directory totals
|
||||||
|
-l use a long listing format
|
||||||
|
-L, --dereference when showing file information for a symbolic
|
||||||
|
link, show information for the file the link
|
||||||
|
references rather than for the link itself
|
||||||
|
-m fill width with a comma separated list of entries
|
||||||
|
-n, --numeric-uid-gid like -l, but list numeric user and group IDs
|
||||||
|
-N, --literal print entry names without quoting
|
||||||
|
-o like -l, but do not list group information
|
||||||
|
-p, --indicator-style=slash
|
||||||
|
append / indicator to directories
|
||||||
|
-q, --hide-control-chars print ? instead of nongraphic characters
|
||||||
|
--show-control-chars show nongraphic characters as-is (the default,
|
||||||
|
unless program is 'ls' and output is a terminal)
|
||||||
|
-Q, --quote-name enclose entry names in double quotes
|
||||||
|
--quoting-style=WORD use quoting style WORD for entry names:
|
||||||
|
literal, locale, shell, shell-always,
|
||||||
|
shell-escape, shell-escape-always, c, escape
|
||||||
|
(overrides QUOTING_STYLE environment variable)
|
||||||
|
-r, --reverse reverse order while sorting
|
||||||
|
-R, --recursive list subdirectories recursively
|
||||||
|
-s, --size print the allocated size of each file, in blocks
|
||||||
|
-S sort by file size, largest first
|
||||||
|
--sort=WORD sort by WORD instead of name: none (-U), size (-S),
|
||||||
|
time (-t), version (-v), extension (-X)
|
||||||
|
--time=WORD with -l, show time as WORD instead of default
|
||||||
|
modification time: atime or access or use (-u);
|
||||||
|
ctime or status (-c); also use specified time
|
||||||
|
as sort key if --sort=time (newest first)
|
||||||
|
--time-style=TIME_STYLE time/date format with -l; see TIME_STYLE below
|
||||||
|
-t sort by modification time, newest first
|
||||||
|
-T, --tabsize=COLS assume tab stops at each COLS instead of 8
|
||||||
|
-u with -lt: sort by, and show, access time;
|
||||||
|
with -l: show access time and sort by name;
|
||||||
|
otherwise: sort by access time, newest first
|
||||||
|
-U do not sort; list entries in directory order
|
||||||
|
-v natural sort of (version) numbers within text
|
||||||
|
-w, --width=COLS set output width to COLS. 0 means no limit
|
||||||
|
-x list entries by lines instead of by columns
|
||||||
|
-X sort alphabetically by entry extension
|
||||||
|
-Z, --context print any security context of each file
|
||||||
|
-1 list one file per line. Avoid '\n' with -q or -b
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit
|
||||||
|
|
||||||
|
The SIZE argument is an integer and optional unit (example: 10K is 10*1024).
|
||||||
|
Units are K,M,G,T,P,E,Z,Y (powers of 1024) or KB,MB,... (powers of 1000).
|
||||||
|
|
||||||
|
The TIME_STYLE argument can be full-iso, long-iso, iso, locale, or +FORMAT.
|
||||||
|
FORMAT is interpreted like in date(1). If FORMAT is FORMAT1<newline>FORMAT2,
|
||||||
|
then FORMAT1 applies to non-recent files and FORMAT2 to recent files.
|
||||||
|
TIME_STYLE prefixed with 'posix-' takes effect only outside the POSIX locale.
|
||||||
|
Also the TIME_STYLE environment variable sets the default style to use.
|
||||||
|
|
||||||
|
Using color to distinguish file types is disabled both by default and
|
||||||
|
with --color=never. With --color=auto, ls emits color codes only when
|
||||||
|
standard output is connected to a terminal. The LS_COLORS environment
|
||||||
|
variable can change the settings. Use the dircolors command to set it.
|
||||||
|
|
||||||
|
Exit status:
|
||||||
|
0 if OK,
|
||||||
|
1 if minor problems (e.g., cannot access subdirectory),
|
||||||
|
2 if serious trouble (e.g., cannot access command-line argument).
|
||||||
|
|
||||||
|
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
|
||||||
|
Report ls translation bugs to <https://translationproject.org/team/>
|
||||||
|
Full documentation at: <https://www.gnu.org/software/coreutils/ls>
|
||||||
|
or available locally via: info '(coreutils) ls invocation'`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mkdir struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mkdir Mkdir) Handle(cmdInfo *info.Info) {
|
||||||
|
if _, files, ok := basicFileCommandParser(cmdInfo, mkdir.help()); ok {
|
||||||
|
for _, file := range files {
|
||||||
|
switch _, err := cmdInfo.FS.CreateDirectory(file); err {
|
||||||
|
case os.ErrExist:
|
||||||
|
cmdInfo.Writelnf("%s: cannot create directory '%s': File exists", cmdInfo.Command, file)
|
||||||
|
case os.ErrPermission:
|
||||||
|
cmdInfo.Writelnf("%s: cannot create directory '%s': Permission denied", cmdInfo.Command, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mkdir Mkdir) help() string {
|
||||||
|
return `
|
||||||
|
Usage: mkdir [OPTION]... DIRECTORY...
|
||||||
|
Create the DIRECTORY(ies), if they do not already exist.
|
||||||
|
|
||||||
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
-m, --mode=MODE set file mode (as in chmod), not a=rwx - umask
|
||||||
|
-p, --parents no error if existing, make parent directories as needed
|
||||||
|
-v, --verbose print a message for each created directory
|
||||||
|
-Z set SELinux security context of each created directory
|
||||||
|
to the default type
|
||||||
|
--context[=CTX] like -Z, or if CTX is specified then set the SELinux
|
||||||
|
or SMACK security context to CTX
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit
|
||||||
|
|
||||||
|
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
|
||||||
|
Report mkdir translation bugs to <https://translationproject.org/team/>
|
||||||
|
Full documentation at: <https://www.gnu.org/software/coreutils/mkdir>
|
||||||
|
or available locally via: info '(coreutils) mkdir invocation'`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RM struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm RM) Handle(cmdInfo *info.Info) {
|
||||||
|
if options, files, ok := basicFileCommandParser(cmdInfo, rm.help()); ok {
|
||||||
|
var dir bool
|
||||||
|
for _, arg := range options {
|
||||||
|
switch arg {
|
||||||
|
case "-r", "-R", "--recursive":
|
||||||
|
dir = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fileName := range files {
|
||||||
|
if cmdInfo.FS.Manipulate {
|
||||||
|
if file, ok := cmdInfo.FS.GetFile(fileName, false); ok {
|
||||||
|
if file.IsDir() && !dir {
|
||||||
|
cmdInfo.Writelnf("rm: cannot remove '%s': Is a directory", fileName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
file.Remove()
|
||||||
|
} else {
|
||||||
|
cmdInfo.Writelnf("rm: cannot remove '%s': No such file or directory", fileName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmdInfo.Writef("rm: remove write-protected file '%s'? ", fileName)
|
||||||
|
switch line, _ := cmdInfo.Read(); line {
|
||||||
|
case "y", "Y":
|
||||||
|
cmdInfo.Writelnf("rm: cannot remove '%s': Operation not permitted", fileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rm RM) help() string {
|
||||||
|
return `
|
||||||
|
Usage: rm [OPTION]... [FILE]...
|
||||||
|
Remove (unlink) the FILE(s).
|
||||||
|
|
||||||
|
-f, --force ignore nonexistent files and arguments, never prompt
|
||||||
|
-i prompt before every removal
|
||||||
|
-I prompt once before removing more than three files, or
|
||||||
|
when removing recursively; less intrusive than -i,
|
||||||
|
while still giving protection against most mistakes
|
||||||
|
--interactive[=WHEN] prompt according to WHEN: never, once (-I), or
|
||||||
|
always (-i); without WHEN, prompt always
|
||||||
|
--one-file-system when removing a hierarchy recursively, skip any
|
||||||
|
directory that is on a file system different from
|
||||||
|
that of the corresponding command line argument
|
||||||
|
--no-preserve-root do not treat '/' specially
|
||||||
|
--preserve-root[=all] do not remove '/' (default);
|
||||||
|
with 'all', reject any command line argument
|
||||||
|
on a separate device from its parent
|
||||||
|
-r, -R, --recursive remove directories and their contents recursively
|
||||||
|
-d, --dir remove empty directories
|
||||||
|
-v, --verbose explain what is being done
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit
|
||||||
|
|
||||||
|
By default, rm does not remove directories. Use the --recursive (-r or -R)
|
||||||
|
option to remove each listed directory, too, along with all of its contents.
|
||||||
|
|
||||||
|
To remove a file whose name starts with a '-', for example '-foo',
|
||||||
|
use one of these commands:
|
||||||
|
rm -- -foo
|
||||||
|
|
||||||
|
rm ./-foo
|
||||||
|
|
||||||
|
Note that if you use rm to remove a file, it might be possible to recover
|
||||||
|
some of its contents, given sufficient expertise and/or time. For greater
|
||||||
|
assurance that the contents are truly unrecoverable, consider using shred.
|
||||||
|
|
||||||
|
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
|
||||||
|
Report rm translation bugs to <https://translationproject.org/team/>
|
||||||
|
Full documentation at: <https://www.gnu.org/software/coreutils/rm>
|
||||||
|
or available locally via: info '(coreutils) rm invocation'
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Touch struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (touch Touch) Handle(cmdInfo *info.Info) {
|
||||||
|
if _, files, ok := basicFileCommandParser(cmdInfo, touch.help()); ok {
|
||||||
|
for _, file := range files {
|
||||||
|
if _, err := cmdInfo.FS.CreateFile(file); err == os.ErrPermission {
|
||||||
|
cmdInfo.Writelnf("%s: cannot touch '%s': Permission denied", cmdInfo.Command, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (touch Touch) help() string {
|
||||||
|
return `Usage: touch [OPTION]... FILE...
|
||||||
|
Update the access and modification times of each FILE to the current time.
|
||||||
|
|
||||||
|
A FILE argument that does not exist is created empty, unless -c or -h
|
||||||
|
is supplied.
|
||||||
|
|
||||||
|
A FILE argument string of - is handled specially and causes touch to
|
||||||
|
change the times of the file associated with standard output.
|
||||||
|
|
||||||
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
-a change only the access time
|
||||||
|
-c, --no-create do not create any files
|
||||||
|
-d, --date=STRING parse STRING and use it instead of current time
|
||||||
|
-f (ignored)
|
||||||
|
-h, --no-dereference affect each symbolic link instead of any referenced
|
||||||
|
file (useful only on systems that can change the
|
||||||
|
timestamps of a symlink)
|
||||||
|
-m change only the modification time
|
||||||
|
-r, --reference=FILE use this file's times instead of current time
|
||||||
|
-t STAMP use [[CC]YY]MMDDhhmm[.ss] instead of current time
|
||||||
|
--time=WORD change the specified time:
|
||||||
|
WORD is access, atime, or use: equivalent to -a
|
||||||
|
WORD is modify or mtime: equivalent to -m
|
||||||
|
--help display this help and exit
|
||||||
|
--version output version information and exit
|
||||||
|
|
||||||
|
Note that the -d and -t options accept different time-date formats.
|
||||||
|
|
||||||
|
GNU coreutils online help: <https://www.gnu.org/software/coreutils/>
|
||||||
|
Report touch translation bugs to <https://translationproject.org/team/>
|
||||||
|
Full documentation at: <https://www.gnu.org/software/coreutils/touch>
|
||||||
|
or available locally via: info '(coreutils) touch invocation'`
|
||||||
|
}
|
27
sshoneypot/handler/handler.go
Normal file
27
sshoneypot/handler/handler.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Default(info *info.Info) {
|
||||||
|
info.Writelnf("-bash: %s: command not found", info.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func multipleArgs(info *info.Info) bool {
|
||||||
|
if len(info.Args) == 0 {
|
||||||
|
info.Writeln(missingOperand(info))
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func missingOperand(info *info.Info) string {
|
||||||
|
return fmt.Sprintf("%s: missing operand", info.Command)
|
||||||
|
}
|
||||||
|
|
||||||
|
func noSuchFileOrDirectory(file string, info *info.Info) string {
|
||||||
|
return fmt.Sprintf("%s: %s: No such file or directory", info.Command, file)
|
||||||
|
}
|
382
sshoneypot/handler/net.go
Normal file
382
sshoneypot/handler/net.go
Normal file
@ -0,0 +1,382 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
"github.com/pborman/getopt"
|
||||||
|
"math/rand"
|
||||||
|
"mime"
|
||||||
|
"net"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func randomIp() string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d.%d", rand.Intn(254), rand.Intn(254), rand.Intn(254), rand.Intn(254))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Ifconfig(cmdInfo *info.Info) {
|
||||||
|
for _, arg := range cmdInfo.Args {
|
||||||
|
if arg == "--help" {
|
||||||
|
cmdInfo.Writeln(`Usage:
|
||||||
|
ifconfig [-a] [-v] [-s] <interface> [[<AF>] <address>]
|
||||||
|
[add <address>[/<prefixlen>]]
|
||||||
|
[del <address>[/<prefixlen>]]
|
||||||
|
[[-]broadcast [<address>]] [[-]pointopoint [<address>]]
|
||||||
|
[netmask <address>] [dstaddr <address>] [tunnel <address>]
|
||||||
|
[outfill <NN>] [keepalive <NN>]
|
||||||
|
[hw <HW> <address>] [mtu <NN>]
|
||||||
|
[[-]trailers] [[-]arp] [[-]allmulti]
|
||||||
|
[multicast] [[-]promisc]
|
||||||
|
[mem_start <NN>] [io_addr <NN>] [irq <NN>] [media <type>]
|
||||||
|
[txqueuelen <NN>]
|
||||||
|
[[-]dynamic]
|
||||||
|
[up|down] ...
|
||||||
|
|
||||||
|
<HW>=Hardware Type.
|
||||||
|
List of possible hardware types:
|
||||||
|
loop (Local Loopback) slip (Serial Line IP) cslip (VJ Serial Line IP)
|
||||||
|
slip6 (6-bit Serial Line IP) cslip6 (VJ 6-bit Serial Line IP) adaptive (Adaptive Serial Line IP)
|
||||||
|
ash (Ash) ether (Ethernet) ax25 (AMPR AX.25)
|
||||||
|
netrom (AMPR NET/ROM) rose (AMPR ROSE) tunnel (IPIP Tunnel)
|
||||||
|
ppp (Point-to-Point Protocol) hdlc ((Cisco)-HDLC) lapb (LAPB)
|
||||||
|
arcnet (ARCnet) dlci (Frame Relay DLCI) frad (Frame Relay Access Device)
|
||||||
|
sit (IPv6-in-IPv4) fddi (Fiber Distributed Data Interface) hippi (HIPPI)
|
||||||
|
irda (IrLAP) ec (Econet) x25 (generic X.25)
|
||||||
|
eui64 (Generic EUI-64)
|
||||||
|
<AF>=Address family. Default: inet
|
||||||
|
List of possible address families:
|
||||||
|
unix (UNIX Domain) inet (DARPA Internet) inet6 (IPv6)
|
||||||
|
ax25 (AMPR AX.25) netrom (AMPR NET/ROM) rose (AMPR ROSE)
|
||||||
|
ipx (Novell IPX) ddp (Appletalk DDP) ec (Econet)
|
||||||
|
ash (Ash) x25 (CCITT X.25)`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmdInfo.Writeln(`eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
|
||||||
|
inet 172.17.0.2 netmask 255.255.0.0 broadcast 172.17.255.255
|
||||||
|
ether 02:42:ac:11:00:02 txqueuelen 0 (Ethernet)
|
||||||
|
RX packets 13 bytes 1182 (1.1 KiB)
|
||||||
|
RX errors 0 dropped 0 overruns 0 frame 0
|
||||||
|
TX packets 0 bytes 0 (0.0 B)
|
||||||
|
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
|
||||||
|
|
||||||
|
lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
|
||||||
|
inet 127.0.0.1 netmask 255.0.0.0
|
||||||
|
loop txqueuelen 1000 (Local Loopback)
|
||||||
|
RX packets 0 bytes 0 (0.0 B)
|
||||||
|
RX errors 0 dropped 0 overruns 0 frame 0
|
||||||
|
TX packets 0 bytes 0 (0.0 B)
|
||||||
|
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0`)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ping struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ping Ping) Handle(cmdInfo *info.Info) {
|
||||||
|
opts := getopt.New()
|
||||||
|
|
||||||
|
count := opts.Int('c', 4, "count")
|
||||||
|
interval := opts.String('i', "1", "interval")
|
||||||
|
packetsize := opts.Int('s', 56, "packetsize")
|
||||||
|
opts.Getopt(cmdInfo.FullArgs, nil)
|
||||||
|
|
||||||
|
duration, err := strconv.ParseFloat(*interval, 64)
|
||||||
|
if err != nil {
|
||||||
|
cmdInfo.Writelnf("%s: bad timing interval", cmdInfo.Command)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
address := opts.Arg(opts.NArgs() - 1)
|
||||||
|
ip := address
|
||||||
|
if net.ParseIP(address) == nil {
|
||||||
|
ip = randomIp()
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdInfo.Writelnf("PING %s (%s) %d data bytes.", address, ip, *packetsize)
|
||||||
|
for i := 0; i < *count; i++ {
|
||||||
|
resultTime := rand.Float64() * 10
|
||||||
|
cmdInfo.Writelnf("%d bytes from %s (%s): icmp_seq=%d ttl=119 time=%.2f", *packetsize + 8, address, ip, i + 1, resultTime)
|
||||||
|
time.Sleep(time.Duration(duration) * time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ping Ping) help() string {
|
||||||
|
return `Usage: ping [-aAbBdDfhLnOqrRUvV64] [-c count] [-i interval] [-I interface]
|
||||||
|
[-m mark] [-M pmtudisc_option] [-l preload] [-p pattern] [-Q tos]
|
||||||
|
[-s packetsize] [-S sndbuf] [-t ttl] [-T timestamp_option]
|
||||||
|
[-w deadline] [-W timeout] [hop1 ...] destination
|
||||||
|
Usage: ping -6 [-aAbBdDfhLnOqrRUvV] [-c count] [-i interval] [-I interface]
|
||||||
|
[-l preload] [-m mark] [-M pmtudisc_option]
|
||||||
|
[-N nodeinfo_option] [-p pattern] [-Q tclass] [-s packetsize]
|
||||||
|
[-S sndbuf] [-t ttl] [-T timestamp_option] [-w deadline]
|
||||||
|
[-W timeout] destination`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Wget struct {
|
||||||
|
info.SimpleHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wget Wget) Handle(cmdInfo *info.Info) {
|
||||||
|
if _, urls, ok := basicFileCommandParser(cmdInfo, wget.help()); ok {
|
||||||
|
regex := regexp.MustCompile("/((([A-Za-z]{3,9}:(?:\\/\\/)?)(?:[-;:&=\\+\\$,\\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=\\+\\$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[\\+~%\\/.\\w-_]*)?\\??(?:[-\\+=&;%@.\\w_]*)#?(?:[\\w]*))?)/")
|
||||||
|
for _, url := range urls {
|
||||||
|
var port uint32
|
||||||
|
schemeUrl := url
|
||||||
|
|
||||||
|
if strings.HasPrefix(url, "http://") {
|
||||||
|
port = 80
|
||||||
|
} else if strings.HasPrefix(url, "https://") {
|
||||||
|
port = 433
|
||||||
|
} else {
|
||||||
|
schemeUrl = "http://" + url
|
||||||
|
port = 433
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTime := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
if regex.MatchString(url) {
|
||||||
|
// generates a random ip
|
||||||
|
|
||||||
|
mimeType := mime.TypeByExtension(url)
|
||||||
|
if mimeType == "" {
|
||||||
|
mimeType = "text/html"
|
||||||
|
}
|
||||||
|
|
||||||
|
ip := randomIp()
|
||||||
|
|
||||||
|
cmdInfo.Writelnf(`--%s-- %s
|
||||||
|
Connecting to %s (%s)|%s|:%d... connected."
|
||||||
|
HTTP request sent, awaiting response... 200 OK
|
||||||
|
Length: unspecified [%s]
|
||||||
|
Saving to: '%s'`, currentTime, schemeUrl, url, url, ip, port, mimeType, url)
|
||||||
|
|
||||||
|
// TODO: download bar
|
||||||
|
|
||||||
|
// sleeps a random time to imitate the fetching process
|
||||||
|
time.Sleep(time.Duration(rand.Intn(5000)) * time.Millisecond)
|
||||||
|
|
||||||
|
if cmdInfo.Terminal.Width >= 60 {
|
||||||
|
loadingBar := 1
|
||||||
|
if cmdInfo.Terminal.Width > 60 {
|
||||||
|
loadingBar += int((cmdInfo.Terminal.Width - 60) / 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cmdInfo.Writelnf(`--%s-- %s
|
||||||
|
Resolving %s (%s)... failed: Name or service not known.
|
||||||
|
wget: unable to resolve host address '%s'`, currentTime, schemeUrl, url, url, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (wget Wget) help() string {
|
||||||
|
return `GNU Wget 1.20.1, a non-interactive network retriever.
|
||||||
|
Usage: wget [OPTION]... [URL]...
|
||||||
|
|
||||||
|
Mandatory arguments to long options are mandatory for short options too.
|
||||||
|
|
||||||
|
Startup:
|
||||||
|
-V, --version display the version of Wget and exit
|
||||||
|
-h, --help print this help
|
||||||
|
-b, --background go to background after startup
|
||||||
|
-e, --execute=COMMAND execute a '.wgetrc'-style command
|
||||||
|
|
||||||
|
Logging and input file:
|
||||||
|
-o, --output-file=FILE log messages to FILE
|
||||||
|
-a, --append-output=FILE append messages to FILE
|
||||||
|
-d, --debug print lots of debugging information
|
||||||
|
-q, --quiet quiet (no output)
|
||||||
|
-v, --verbose be verbose (this is the default)
|
||||||
|
-nv, --no-verbose turn off verboseness, without being quiet
|
||||||
|
--report-speed=TYPE output bandwidth as TYPE. TYPE can be bits
|
||||||
|
-i, --input-file=FILE download URLs found in local or external FILE
|
||||||
|
-F, --force-html treat input file as HTML
|
||||||
|
-B, --base=URL resolves HTML input-file links (-i -F)
|
||||||
|
relative to URL
|
||||||
|
--config=FILE specify config file to use
|
||||||
|
--no-config do not read any config file
|
||||||
|
--rejected-log=FILE log reasons for URL rejection to FILE
|
||||||
|
|
||||||
|
Download:
|
||||||
|
-t, --tries=NUMBER set number of retries to NUMBER (0 unlimits)
|
||||||
|
--retry-connrefused retry even if connection is refused
|
||||||
|
--retry-on-http-error=ERRORS comma-separated list of HTTP errors to retry
|
||||||
|
-O, --output-document=FILE write documents to FILE
|
||||||
|
-nc, --no-clobber skip downloads that would download to
|
||||||
|
existing files (overwriting them)
|
||||||
|
--no-netrc don't try to obtain credentials from .netrc
|
||||||
|
-c, --continue resume getting a partially-downloaded file
|
||||||
|
--start-pos=OFFSET start downloading from zero-based position OFFSET
|
||||||
|
--progress=TYPE select progress gauge type
|
||||||
|
--show-progress display the progress bar in any verbosity mode
|
||||||
|
-N, --timestamping don't re-retrieve files unless newer than
|
||||||
|
local
|
||||||
|
--no-if-modified-since don't use conditional if-modified-since get
|
||||||
|
requests in timestamping mode
|
||||||
|
--no-use-server-timestamps don't set the local file's timestamp by
|
||||||
|
the one on the server
|
||||||
|
-S, --server-response print server response
|
||||||
|
--spider don't download anything
|
||||||
|
-T, --timeout=SECONDS set all timeout values to SECONDS
|
||||||
|
--dns-timeout=SECS set the DNS lookup timeout to SECS
|
||||||
|
--connect-timeout=SECS set the connect timeout to SECS
|
||||||
|
--read-timeout=SECS set the read timeout to SECS
|
||||||
|
-w, --wait=SECONDS wait SECONDS between retrievals
|
||||||
|
--waitretry=SECONDS wait 1..SECONDS between retries of a retrieval
|
||||||
|
--random-wait wait from 0.5*WAIT...1.5*WAIT secs between retrievals
|
||||||
|
--no-proxy explicitly turn off proxy
|
||||||
|
-Q, --quota=NUMBER set retrieval quota to NUMBER
|
||||||
|
--bind-address=ADDRESS bind to ADDRESS (hostname or IP) on local host
|
||||||
|
--limit-rate=RATE limit download rate to RATE
|
||||||
|
--no-dns-cache disable caching DNS lookups
|
||||||
|
--restrict-file-names=OS restrict chars in file names to ones OS allows
|
||||||
|
--ignore-case ignore case when matching files/directories
|
||||||
|
-4, --inet4-only connect only to IPv4 addresses
|
||||||
|
-6, --inet6-only connect only to IPv6 addresses
|
||||||
|
--prefer-family=FAMILY connect first to addresses of specified family,
|
||||||
|
one of IPv6, IPv4, or none
|
||||||
|
--user=USER set both ftp and http user to USER
|
||||||
|
--password=PASS set both ftp and http password to PASS
|
||||||
|
--ask-password prompt for passwords
|
||||||
|
--use-askpass=COMMAND specify credential handler for requesting
|
||||||
|
username and password. If no COMMAND is
|
||||||
|
specified the WGET_ASKPASS or the SSH_ASKPASS
|
||||||
|
environment variable is used.
|
||||||
|
--no-iri turn off IRI support
|
||||||
|
--local-encoding=ENC use ENC as the local encoding for IRIs
|
||||||
|
--remote-encoding=ENC use ENC as the default remote encoding
|
||||||
|
--unlink remove file before clobber
|
||||||
|
--xattr turn on storage of metadata in extended file attributes
|
||||||
|
|
||||||
|
Directories:
|
||||||
|
-nd, --no-directories don't create directories
|
||||||
|
-x, --force-directories force creation of directories
|
||||||
|
-nH, --no-host-directories don't create host directories
|
||||||
|
--protocol-directories use protocol name in directories
|
||||||
|
-P, --directory-prefix=PREFIX save files to PREFIX/..
|
||||||
|
--cut-dirs=NUMBER ignore NUMBER remote directory components
|
||||||
|
|
||||||
|
HTTP options:
|
||||||
|
--http-user=USER set http user to USER
|
||||||
|
--http-password=PASS set http password to PASS
|
||||||
|
--no-cache disallow server-cached data
|
||||||
|
--default-page=NAME change the default page name (normally
|
||||||
|
this is 'index.html'.)
|
||||||
|
-E, --adjust-extension save HTML/CSS documents with proper extensions
|
||||||
|
--ignore-length ignore 'Content-Length' header field
|
||||||
|
--header=STRING insert STRING among the headers
|
||||||
|
--compression=TYPE choose compression, one of auto, gzip and none. (default: none)
|
||||||
|
--max-redirect maximum redirections allowed per page
|
||||||
|
--proxy-user=USER set USER as proxy username
|
||||||
|
--proxy-password=PASS set PASS as proxy password
|
||||||
|
--referer=URL include 'Referer: URL' header in HTTP request
|
||||||
|
--save-headers save the HTTP headers to file
|
||||||
|
-U, --user-agent=AGENT identify as AGENT instead of Wget/VERSION
|
||||||
|
--no-http-keep-alive disable HTTP keep-alive (persistent connections)
|
||||||
|
--no-cookies don't use cookies
|
||||||
|
--load-cookies=FILE load cookies from FILE before session
|
||||||
|
--save-cookies=FILE save cookies to FILE after session
|
||||||
|
--keep-session-cookies load and save session (non-permanent) cookies
|
||||||
|
--post-data=STRING use the POST method; send STRING as the data
|
||||||
|
--post-file=FILE use the POST method; send contents of FILE
|
||||||
|
--method=HTTPMethod use method "HTTPMethod" in the request
|
||||||
|
--body-data=STRING send STRING as data. --method MUST be set
|
||||||
|
--body-file=FILE send contents of FILE. --method MUST be set
|
||||||
|
--content-disposition honor the Content-Disposition header when
|
||||||
|
choosing local file names (EXPERIMENTAL)
|
||||||
|
--content-on-error output the received content on server errors
|
||||||
|
--auth-no-challenge send Basic HTTP authentication information
|
||||||
|
without first waiting for the server's
|
||||||
|
challenge
|
||||||
|
|
||||||
|
HTTPS (SSL/TLS) options:
|
||||||
|
--secure-protocol=PR choose secure protocol, one of auto, SSLv2,
|
||||||
|
SSLv3, TLSv1, TLSv1_1, TLSv1_2 and PFS
|
||||||
|
--https-only only follow secure HTTPS links
|
||||||
|
--no-check-certificate don't validate the server's certificate
|
||||||
|
--certificate=FILE client certificate file
|
||||||
|
--certificate-type=TYPE client certificate type, PEM or DER
|
||||||
|
--private-key=FILE private key file
|
||||||
|
--private-key-type=TYPE private key type, PEM or DER
|
||||||
|
--ca-certificate=FILE file with the bundle of CAs
|
||||||
|
--ca-directory=DIR directory where hash list of CAs is stored
|
||||||
|
--crl-file=FILE file with bundle of CRLs
|
||||||
|
--pinnedpubkey=FILE/HASHES Public key (PEM/DER) file, or any number
|
||||||
|
of base64 encoded sha256 hashes preceded by
|
||||||
|
'sha256//' and separated by ';', to verify
|
||||||
|
peer against
|
||||||
|
|
||||||
|
--ciphers=STR Set the priority string (GnuTLS) or cipher list string (OpenSSL) directly.
|
||||||
|
Use with care. This option overrides --secure-protocol.
|
||||||
|
The format and syntax of this string depend on the specific SSL/TLS engine.
|
||||||
|
HSTS options:
|
||||||
|
--no-hsts disable HSTS
|
||||||
|
--hsts-file path of HSTS database (will override default)
|
||||||
|
|
||||||
|
FTP options:
|
||||||
|
--ftp-user=USER set ftp user to USER
|
||||||
|
--ftp-password=PASS set ftp password to PASS
|
||||||
|
--no-remove-listing don't remove '.listing' files
|
||||||
|
--no-glob turn off FTP file name globbing
|
||||||
|
--no-passive-ftp disable the "passive" transfer mode
|
||||||
|
--preserve-permissions preserve remote file permissions
|
||||||
|
--retr-symlinks when recursing, get linked-to files (not dir)
|
||||||
|
|
||||||
|
FTPS options:
|
||||||
|
--ftps-implicit use implicit FTPS (default port is 990)
|
||||||
|
--ftps-resume-ssl resume the SSL/TLS session started in the control connection when
|
||||||
|
opening a data connection
|
||||||
|
--ftps-clear-data-connection cipher the control channel only; all the data will be in plaintext
|
||||||
|
--ftps-fallback-to-ftp fall back to FTP if FTPS is not supported in the target server
|
||||||
|
WARC options:
|
||||||
|
--warc-file=FILENAME save request/response data to a .warc.gz file
|
||||||
|
--warc-header=STRING insert STRING into the warcinfo record
|
||||||
|
--warc-max-size=NUMBER set maximum size of WARC files to NUMBER
|
||||||
|
--warc-cdx write CDX index files
|
||||||
|
--warc-dedup=FILENAME do not store records listed in this CDX file
|
||||||
|
--no-warc-compression do not compress WARC files with GZIP
|
||||||
|
--no-warc-digests do not calculate SHA1 digests
|
||||||
|
--no-warc-keep-log do not store the log file in a WARC record
|
||||||
|
--warc-tempdir=DIRECTORY location for temporary files created by the
|
||||||
|
WARC writer
|
||||||
|
|
||||||
|
Recursive download:
|
||||||
|
-r, --recursive specify recursive download
|
||||||
|
-l, --level=NUMBER maximum recursion depth (inf or 0 for infinite)
|
||||||
|
--delete-after delete files locally after downloading them
|
||||||
|
-k, --convert-links make links in downloaded HTML or CSS point to
|
||||||
|
local files
|
||||||
|
--convert-file-only convert the file part of the URLs only (usually known as the basename)
|
||||||
|
--backups=N before writing file X, rotate up to N backup files
|
||||||
|
-K, --backup-converted before converting file X, back up as X.orig
|
||||||
|
-m, --mirror shortcut for -N -r -l inf --no-remove-listing
|
||||||
|
-p, --page-requisites get all images, etc. needed to display HTML page
|
||||||
|
--strict-comments turn on strict (SGML) handling of HTML comments
|
||||||
|
|
||||||
|
Recursive accept/reject:
|
||||||
|
-A, --accept=LIST comma-separated list of accepted extensions
|
||||||
|
-R, --reject=LIST comma-separated list of rejected extensions
|
||||||
|
--accept-regex=REGEX regex matching accepted URLs
|
||||||
|
--reject-regex=REGEX regex matching rejected URLs
|
||||||
|
--regex-type=TYPE regex type (posix|pcre)
|
||||||
|
-D, --domains=LIST comma-separated list of accepted domains
|
||||||
|
--exclude-domains=LIST comma-separated list of rejected domains
|
||||||
|
--follow-ftp follow FTP links from HTML documents
|
||||||
|
--follow-tags=LIST comma-separated list of followed HTML tags
|
||||||
|
--ignore-tags=LIST comma-separated list of ignored HTML tags
|
||||||
|
-H, --span-hosts go to foreign hosts when recursive
|
||||||
|
-L, --relative follow relative links only
|
||||||
|
-I, --include-directories=LIST list of allowed directories
|
||||||
|
--trust-server-names use the name specified by the redirection
|
||||||
|
URL's last component
|
||||||
|
-X, --exclude-directories=LIST list of excluded directories
|
||||||
|
-np, --no-parent don't ascend to the parent directory
|
||||||
|
|
||||||
|
Email bug reports, questions, discussions to <bug-wget@gnu.org>
|
||||||
|
and/or open issues at https://savannah.gnu.org/bugs/?func=additem&group=wget.`
|
||||||
|
}
|
12
sshoneypot/handler/out.go
Normal file
12
sshoneypot/handler/out.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Echo(cmdInfo *info.Info) {
|
||||||
|
if options, files, ok := basicFileCommandParser(cmdInfo, "--help"); ok {
|
||||||
|
cmdInfo.Writeln(strings.Join(append(options, files...), " "))
|
||||||
|
}
|
||||||
|
}
|
1
sshoneypot/handler/permission.go
Normal file
1
sshoneypot/handler/permission.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package handler
|
7
sshoneypot/handler/screen.go
Normal file
7
sshoneypot/handler/screen.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import "github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
|
||||||
|
func Clear(info *info.Info) {
|
||||||
|
info.Write("\033[H\033[2J")
|
||||||
|
}
|
401
sshoneypot/info/fs.go
Normal file
401
sshoneypot/info/fs.go
Normal file
@ -0,0 +1,401 @@
|
|||||||
|
package info
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// file structure extracted from a debian buster docker container including:
|
||||||
|
// - curl 7.64.0
|
||||||
|
// - net-tools
|
||||||
|
// - OpenSSH_7.9p1
|
||||||
|
// - python3.7.3
|
||||||
|
// - wget 1.20.1
|
||||||
|
// - and all dependencies of the installed packages
|
||||||
|
|
||||||
|
const (
|
||||||
|
TypeDirectory = 4
|
||||||
|
TypeCharacterDevice = 2
|
||||||
|
TypeBlockDevice = 6
|
||||||
|
TypeRegularFile = 10
|
||||||
|
TypeFifo = 1
|
||||||
|
TypeSymbolicLink = 12
|
||||||
|
TypeSocketFile = 14
|
||||||
|
)
|
||||||
|
|
||||||
|
type BasicFile interface {
|
||||||
|
// This returns the directory in which the file is located.
|
||||||
|
// Be careful to not mix it up with Directory.Files
|
||||||
|
Directory() (Directory, bool)
|
||||||
|
RawDirectory() map[string]interface{}
|
||||||
|
|
||||||
|
// If the file a dir
|
||||||
|
IsDir() bool
|
||||||
|
|
||||||
|
// Removes the file and all sub files if the file is a directory
|
||||||
|
Remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Directory struct {
|
||||||
|
BasicFile
|
||||||
|
File
|
||||||
|
|
||||||
|
Files []File
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dir Directory) Directory() (Directory, bool) {return dir.File.Directory()}
|
||||||
|
func (dir Directory) RawDirectory() map[string]interface{} {
|
||||||
|
return dir.dir
|
||||||
|
}
|
||||||
|
func (dir Directory) IsDir() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (dir Directory) Remove() {
|
||||||
|
dir.File.Remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
type File struct {
|
||||||
|
BasicFile
|
||||||
|
fs *FileSystem
|
||||||
|
dir map[string]interface{}
|
||||||
|
location string
|
||||||
|
|
||||||
|
Type uint8
|
||||||
|
|
||||||
|
Size int64
|
||||||
|
Links int
|
||||||
|
Permissions os.FileMode
|
||||||
|
|
||||||
|
Name string
|
||||||
|
CreationTimestamp int64
|
||||||
|
UserID uint16
|
||||||
|
GroupID uint16
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) Directory() (Directory, bool) {
|
||||||
|
if dir, ok, _ := f.fs.getOrCreateFile(filepath.Dir(f.location), false, false, false); !ok {
|
||||||
|
return Directory{}, false
|
||||||
|
} else {
|
||||||
|
return dir.(Directory), true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func (f File) RawDirectory() map[string]interface{} {return f.dir}
|
||||||
|
func (f File) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (f File) Remove() {
|
||||||
|
parent, _ := filepath.Split(f.location)
|
||||||
|
dir, _ := f.fs.GetExplicitDirectory(parent, false)
|
||||||
|
delete(dir.dir["files"].(map[string]interface{}), strings.TrimSuffix(f.Name, string(os.PathSeparator)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f File) Location() string {
|
||||||
|
return f.location
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileSystem struct {
|
||||||
|
fs map[string]interface{}
|
||||||
|
info []interface{}
|
||||||
|
|
||||||
|
currentDir string
|
||||||
|
rawCurrentDir map[string]interface{}
|
||||||
|
|
||||||
|
Manipulate bool
|
||||||
|
|
||||||
|
root bool
|
||||||
|
|
||||||
|
fromFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) Close() error {
|
||||||
|
if fs.Manipulate && fs.fromFile != "" {
|
||||||
|
file, err := os.Open(fs.fromFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return json.NewEncoder(file).Encode(fs.fs)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) CreateDirectory(path string) (Directory, error) {
|
||||||
|
if fs.Manipulate {
|
||||||
|
dir, exist, created := fs.getOrCreateFile(path, false, true, false)
|
||||||
|
if exist {
|
||||||
|
return Directory{}, os.ErrExist
|
||||||
|
} else if !created {
|
||||||
|
return Directory{}, os.ErrPermission
|
||||||
|
} else {
|
||||||
|
return dir.(Directory), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Directory{}, os.ErrPermission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) CreateFile(path string) (File, error) {
|
||||||
|
if fs.Manipulate {
|
||||||
|
file, exist, created := fs.getOrCreateFile(path, false, true, false)
|
||||||
|
if exist {
|
||||||
|
return File{}, os.ErrExist
|
||||||
|
} else if !created {
|
||||||
|
return File{}, os.ErrPermission
|
||||||
|
} else {
|
||||||
|
return file.(File), nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return File{}, os.ErrPermission
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) Cwd() Directory {
|
||||||
|
dir, _ := fs.GetFile("", false)
|
||||||
|
return dir.(Directory)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) Exists(path string) bool {
|
||||||
|
_, exists, _ := fs.getOrCreateFile(path, false, false, false)
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) GetFile(path string, follow bool) (BasicFile, bool) {
|
||||||
|
file, ok, _ := fs.getOrCreateFile(path, follow, false, false)
|
||||||
|
return file, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) GetExplicitDirectory(path string, follow bool) (Directory, bool) {
|
||||||
|
if file, ok, _ := fs.getOrCreateFile(path, follow, false, false); ok {
|
||||||
|
f, ok := file.(Directory)
|
||||||
|
return f, ok
|
||||||
|
} else {
|
||||||
|
return Directory{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) GetExplicitFile(path string) (File, bool) {
|
||||||
|
if file, ok, _ := fs.getOrCreateFile(path, false, false, false); ok {
|
||||||
|
f, ok := file.(File)
|
||||||
|
return f, ok
|
||||||
|
} else {
|
||||||
|
return File{}, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) Walk(path string, walkFunc func(path string, f BasicFile)) bool {
|
||||||
|
if dir, ok := fs.GetExplicitDirectory(path, false); ok {
|
||||||
|
resultDir := dir.RawDirectory()["files"].(map[string]interface{})
|
||||||
|
for name, details := range resultDir {
|
||||||
|
switch details.(type) {
|
||||||
|
case []interface{}:
|
||||||
|
walkFunc(path, fs.generateFileInfo(resultDir, path, name, details.([]interface{})))
|
||||||
|
case map[string]interface{}:
|
||||||
|
resultDir = details.(map[string]interface{})["files"].(map[string]interface{})
|
||||||
|
resultInfo := details.(map[string]interface{})["info"].([]interface{})
|
||||||
|
walkFunc(path, Directory{
|
||||||
|
File: fs.generateFileInfo(resultDir, path, name, resultInfo),
|
||||||
|
Files: fs.directoryFiles(resultDir, path),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) generateFileInfo(resultDir map[string]interface{}, location string, name string, rawInfos []interface{}) File {
|
||||||
|
permissions := os.FileMode(uint32(rawInfos[3].(float64)))
|
||||||
|
|
||||||
|
if uint8(rawInfos[0].(float64)) == 4 {
|
||||||
|
permissions |= os.ModeDir
|
||||||
|
}
|
||||||
|
|
||||||
|
if permissions.IsDir() && !strings.HasSuffix(name, "/") {
|
||||||
|
name += "/"
|
||||||
|
}
|
||||||
|
if strings.Contains(name, " ") && !strings.HasPrefix(name, "'") && !strings.HasSuffix(name, "'") {
|
||||||
|
name = "'" + name + "'"
|
||||||
|
}
|
||||||
|
|
||||||
|
return File{
|
||||||
|
fs: fs,
|
||||||
|
dir: resultDir,
|
||||||
|
location: location,
|
||||||
|
|
||||||
|
Type: uint8(rawInfos[0].(float64)),
|
||||||
|
Size: int64(rawInfos[1].(float64)),
|
||||||
|
Links: int(rawInfos[2].(float64)),
|
||||||
|
Permissions: permissions,
|
||||||
|
|
||||||
|
Name: name,
|
||||||
|
CreationTimestamp: int64(rawInfos[4].(float64)),
|
||||||
|
UserID: uint16(rawInfos[5].(float64)),
|
||||||
|
GroupID: uint16(rawInfos[6].(float64)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) getOrCreateFile(path string, follow bool, create bool, createDir bool) (file BasicFile, exists bool, created bool) {
|
||||||
|
var isFile bool
|
||||||
|
rawDirectory := fs.rawCurrentDir
|
||||||
|
resultDir := fs.rawCurrentDir["files"].(map[string]interface{})
|
||||||
|
var resultFile []interface{}
|
||||||
|
|
||||||
|
if !strings.HasPrefix(path, "/") {
|
||||||
|
path = filepath.Join(fs.currentDir, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
location, name := filepath.Split(path)
|
||||||
|
splitPath := strings.Split(path, string(os.PathSeparator))[1:]
|
||||||
|
|
||||||
|
if path == "/" {
|
||||||
|
resultDir = fs.fs
|
||||||
|
resultFile = fs.info
|
||||||
|
|
||||||
|
location = ""
|
||||||
|
name = "/"
|
||||||
|
splitPath = make([]string, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, s := range splitPath {
|
||||||
|
if s == "" {
|
||||||
|
splitPath = append(splitPath[:i], splitPath[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range splitPath {
|
||||||
|
if dir, ok := resultDir[file]; ok {
|
||||||
|
if preCurrentFile, ok := dir.(map[string]interface{}); ok {
|
||||||
|
rawDirectory = preCurrentFile
|
||||||
|
resultDir = preCurrentFile["files"].(map[string]interface{})
|
||||||
|
resultFile = preCurrentFile["info"].([]interface{})
|
||||||
|
} else if preCurrentFile, ok := dir.([]interface{}); ok {
|
||||||
|
isFile = true
|
||||||
|
resultFile = preCurrentFile
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return nil, false, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resultFile == nil {
|
||||||
|
if create {
|
||||||
|
timestamp := time.Now().Unix()
|
||||||
|
file := File{
|
||||||
|
fs: fs,
|
||||||
|
dir: resultDir,
|
||||||
|
location: location,
|
||||||
|
|
||||||
|
Type: TypeRegularFile,
|
||||||
|
Size: 0,
|
||||||
|
// i dont really know why its 1, linux logic i think
|
||||||
|
Links: 1,
|
||||||
|
Permissions: os.ModeType,
|
||||||
|
|
||||||
|
Name: name,
|
||||||
|
CreationTimestamp: timestamp,
|
||||||
|
UserID: 1000,
|
||||||
|
GroupID: 1000,
|
||||||
|
}
|
||||||
|
if fs.root {
|
||||||
|
file.UserID = 0
|
||||||
|
file.GroupID = 0
|
||||||
|
}
|
||||||
|
if createDir {
|
||||||
|
file.Type = TypeDirectory
|
||||||
|
// i dont really know why its 2, linux logic i think
|
||||||
|
file.Links = 2
|
||||||
|
file.Permissions = os.ModeDir
|
||||||
|
resultDir[name] = map[string]interface{} {"files": make([]string, 0),
|
||||||
|
"info": []interface{} {file.Type, file.Size, file.Links, file.Permissions, file.CreationTimestamp, file.UserID, file.GroupID}}
|
||||||
|
return Directory{
|
||||||
|
BasicFile: file,
|
||||||
|
Files: make([]File, 0),
|
||||||
|
}, false, true
|
||||||
|
} else {
|
||||||
|
resultDir[name] = []interface{} {file.Type, file.Size, file.Links, file.Permissions, file.CreationTimestamp, file.UserID, file.GroupID}
|
||||||
|
}
|
||||||
|
if follow && strings.HasPrefix(path, "/") {
|
||||||
|
fs.currentDir = path
|
||||||
|
} else {
|
||||||
|
fs.currentDir = filepath.Join(fs.currentDir, path)
|
||||||
|
}
|
||||||
|
return file, false, true
|
||||||
|
} else {
|
||||||
|
return nil, false, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
file = fs.generateFileInfo(rawDirectory, location, name, resultFile)
|
||||||
|
|
||||||
|
if !isFile {
|
||||||
|
var files []File
|
||||||
|
|
||||||
|
for fname, info := range resultDir {
|
||||||
|
switch info.(type) {
|
||||||
|
// case normal file
|
||||||
|
case []interface{}:
|
||||||
|
info := info.([]interface{})
|
||||||
|
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
|
||||||
|
// case dir
|
||||||
|
case map[string]interface{}:
|
||||||
|
info := info.(map[string]interface{})["info"].([]interface{})
|
||||||
|
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if follow {
|
||||||
|
fs.currentDir = path
|
||||||
|
}
|
||||||
|
|
||||||
|
return Directory{
|
||||||
|
File: file.(File),
|
||||||
|
Files: fs.directoryFiles(resultDir, location),
|
||||||
|
}, true, false
|
||||||
|
} else {
|
||||||
|
return file, true, false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *FileSystem) directoryFiles(rawDirectory map[string]interface{}, location string) []File {
|
||||||
|
var files []File
|
||||||
|
|
||||||
|
for fname, info := range rawDirectory {
|
||||||
|
switch info.(type) {
|
||||||
|
// case normal file
|
||||||
|
case []interface{}:
|
||||||
|
info := info.([]interface{})
|
||||||
|
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
|
||||||
|
// case dir
|
||||||
|
case map[string]interface{}:
|
||||||
|
info := info.(map[string]interface{})["info"].([]interface{})
|
||||||
|
files = append(files, fs.generateFileInfo(rawDirectory, location, fname, info))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFSFromJson(fsFile string) (*FileSystem, error) {
|
||||||
|
file, err := os.Open(fsFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := make(map[string]interface{})
|
||||||
|
if err := json.NewDecoder(file).Decode(&fs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &FileSystem{
|
||||||
|
fs: fs["files"].(map[string]interface{}),
|
||||||
|
info: fs["info"].([]interface{}),
|
||||||
|
|
||||||
|
rawCurrentDir: fs,
|
||||||
|
currentDir: "/",
|
||||||
|
|
||||||
|
fromFile: fsFile,
|
||||||
|
}, nil
|
||||||
|
}
|
28
sshoneypot/info/handler.go
Normal file
28
sshoneypot/info/handler.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package info
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
//AutoCompleteOptions returns the extra arguments for a command (e.g. the `-a` or `-R` option in the `ls` command)
|
||||||
|
AutoCompleteOptions() []string
|
||||||
|
|
||||||
|
//AutoCompleteRequest gets called if the client request auto complete (e.g. if double pressing the `tab` key)
|
||||||
|
AutoCompleteRequest(info *Info) (accepted bool)
|
||||||
|
|
||||||
|
//Handle will execute / handle the command the client has entered
|
||||||
|
Handle(info *Info)
|
||||||
|
|
||||||
|
//Kill gets called if the user hard stops the command (ctrl + c)
|
||||||
|
Kill()
|
||||||
|
}
|
||||||
|
|
||||||
|
type SimpleHandler struct {
|
||||||
|
Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh SimpleHandler) AutoCompleteOptions() []string {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
func (sh SimpleHandler) AutoCompleteRequest(info *Info) (accepted bool) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (sh SimpleHandler) Handle(info *Info) {}
|
||||||
|
func (sh SimpleHandler) Kill(){}
|
109
sshoneypot/info/info.go
Normal file
109
sshoneypot/info/info.go
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
package info
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/term"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Termina struct {
|
||||||
|
*term.Terminal
|
||||||
|
|
||||||
|
// The current prompt string
|
||||||
|
Prompt string
|
||||||
|
|
||||||
|
Width, Height uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Termina) SetPrompt(prompt string) {
|
||||||
|
t.Prompt = prompt
|
||||||
|
|
||||||
|
t.Terminal.SetPrompt(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Info struct {
|
||||||
|
Conn net.Conn
|
||||||
|
Terminal *Termina
|
||||||
|
|
||||||
|
FS *FileSystem
|
||||||
|
User *ssh.ServerConn
|
||||||
|
|
||||||
|
FullArgs []string
|
||||||
|
Command string
|
||||||
|
Args []string
|
||||||
|
|
||||||
|
Stdout bool
|
||||||
|
Stderr bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Read() (content string, err error) {
|
||||||
|
if info.Terminal != nil {
|
||||||
|
oldPrompt := info.Terminal.Prompt
|
||||||
|
info.Terminal.SetPrompt("")
|
||||||
|
content, err = info.Terminal.ReadLine()
|
||||||
|
info.Terminal.SetPrompt(oldPrompt)
|
||||||
|
} else {
|
||||||
|
var buffer []byte
|
||||||
|
_, err = info.Conn.Read(buffer)
|
||||||
|
content = string(buffer)
|
||||||
|
}
|
||||||
|
return content, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) ReadPrompt() (content string, err error) {
|
||||||
|
if info.Terminal != nil {
|
||||||
|
content, err = info.Terminal.ReadLine()
|
||||||
|
} else {
|
||||||
|
var buffer []byte
|
||||||
|
_, err = info.Conn.Read(buffer)
|
||||||
|
content = string(buffer)
|
||||||
|
}
|
||||||
|
return content, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Write(content string) (err error) {
|
||||||
|
if info.Stdout {
|
||||||
|
if info.Terminal != nil {
|
||||||
|
_, err = info.Terminal.Write([]byte(content))
|
||||||
|
} else {
|
||||||
|
_, err = info.Conn.Write([]byte(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Writef(format string, args... interface{}) error {
|
||||||
|
return info.Write(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Writeln(content string) (err error) {
|
||||||
|
return info.Write(content + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Writelnf(format string, args... interface{}) (err error) {
|
||||||
|
return info.Writeln(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Error(content string) (err error) {
|
||||||
|
if info.Stderr {
|
||||||
|
if info.Terminal != nil {
|
||||||
|
_, err = info.Terminal.Write([]byte(content))
|
||||||
|
} else {
|
||||||
|
_, err = info.Conn.Write([]byte(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Errorf(format string, args... interface{}) error {
|
||||||
|
return info.Error(fmt.Sprintf(format, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Errorln(content string) (err error) {
|
||||||
|
return info.Error(content + "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (info Info) Errorlnf(format string, args... interface{}) (err error) {
|
||||||
|
return info.Errorln(fmt.Sprintf(format, args...))
|
||||||
|
}
|
179
sshoneypot/info/terminal.go
Normal file
179
sshoneypot/info/terminal.go
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
package info
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Terminal struct {
|
||||||
|
// the chan
|
||||||
|
channel ssh.Channel
|
||||||
|
|
||||||
|
// The current prompt string
|
||||||
|
prompt string
|
||||||
|
|
||||||
|
width, height int
|
||||||
|
|
||||||
|
isListening bool
|
||||||
|
buffer []byte
|
||||||
|
|
||||||
|
lock *sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
keyCtrlC = 3
|
||||||
|
keyCtrlD = 4
|
||||||
|
keyEnter = 13
|
||||||
|
keyEscape = 27
|
||||||
|
keyReturn = 127
|
||||||
|
keyUnknown = 0xd800 /* UTF-16 surrogate area */ + iota
|
||||||
|
keyUp
|
||||||
|
keyDown
|
||||||
|
keyLeft
|
||||||
|
keyRight
|
||||||
|
keyAltLeft
|
||||||
|
keyAltRight
|
||||||
|
keyHome
|
||||||
|
keyEnd
|
||||||
|
keyDeleteWord
|
||||||
|
keyDeleteLine
|
||||||
|
keyClearScreen
|
||||||
|
keyPasteStart
|
||||||
|
keyPasteEnd
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CtrlC = errors.New("CtrlC")
|
||||||
|
CtrlD = errors.New("CtrlD")
|
||||||
|
)
|
||||||
|
|
||||||
|
func (term *Terminal) GetSize() (width int, height int) {
|
||||||
|
return term.width, term.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) SetSize(width int, height int) {
|
||||||
|
term.width = width
|
||||||
|
term.height = height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) GetPrompt() string {
|
||||||
|
return term.prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) SetPrompt(prompt string) {
|
||||||
|
term.prompt = prompt
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) ReadLine() (string, error) {
|
||||||
|
var content []byte
|
||||||
|
for {
|
||||||
|
data, _ := term.listen()
|
||||||
|
for _, b := range data {
|
||||||
|
fmt.Println(b)
|
||||||
|
switch []rune(data) {
|
||||||
|
case keyCtrlD:
|
||||||
|
if len(content) == 0 {
|
||||||
|
return "", CtrlD
|
||||||
|
}
|
||||||
|
case keyEnter:
|
||||||
|
term.channel.Write([]byte("\r\n" + term.prompt))
|
||||||
|
return string(data), nil
|
||||||
|
case keyReturn:
|
||||||
|
if len(content) - 1 > len(term.prompt) {
|
||||||
|
content = content[:len(content) - 1]
|
||||||
|
term.MoveCursor(0, 0, 1, 0)
|
||||||
|
term.channel.Write(content)
|
||||||
|
}
|
||||||
|
case keyRight:
|
||||||
|
fmt.Println("aaa")
|
||||||
|
default:
|
||||||
|
content = append(content, b)
|
||||||
|
term.channel.Write(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) Write(data []byte) (n int, err error) {
|
||||||
|
return term.channel.Write(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) MoveCursor(up, down, left, right int) {
|
||||||
|
var m []rune
|
||||||
|
|
||||||
|
// 1 unit up can be expressed as ^[[A or ^[A
|
||||||
|
// 5 units up can be expressed as ^[[5A
|
||||||
|
|
||||||
|
if up == 1 {
|
||||||
|
m = append(m, keyEscape, '[', 'A')
|
||||||
|
} else if up > 1 {
|
||||||
|
m = append(m, keyEscape, '[')
|
||||||
|
m = append(m, []rune(strconv.Itoa(up))...)
|
||||||
|
m = append(m, 'A')
|
||||||
|
}
|
||||||
|
|
||||||
|
if down == 1 {
|
||||||
|
m = append(m, keyEscape, '[', 'B')
|
||||||
|
} else if down > 1 {
|
||||||
|
m = append(m, keyEscape, '[')
|
||||||
|
m = append(m, []rune(strconv.Itoa(down))...)
|
||||||
|
m = append(m, 'B')
|
||||||
|
}
|
||||||
|
|
||||||
|
if right == 1 {
|
||||||
|
m = append(m, keyEscape, '[', 'C')
|
||||||
|
} else if right > 1 {
|
||||||
|
m = append(m, keyEscape, '[')
|
||||||
|
m = append(m, []rune(strconv.Itoa(right))...)
|
||||||
|
m = append(m, 'C')
|
||||||
|
}
|
||||||
|
|
||||||
|
if left == 1 {
|
||||||
|
m = append(m, keyEscape, '[', 'D')
|
||||||
|
} else if left > 1 {
|
||||||
|
m = append(m, keyEscape, '[')
|
||||||
|
m = append(m, []rune(strconv.Itoa(left))...)
|
||||||
|
m = append(m, 'D')
|
||||||
|
}
|
||||||
|
|
||||||
|
term.Write([]byte(string(m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) ListenCtrlC() chan bool {
|
||||||
|
ctrlc := make(chan bool, 1)
|
||||||
|
go func() {
|
||||||
|
if term.isListening {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
data, err := term.listen()
|
||||||
|
if err == CtrlC {
|
||||||
|
ctrlc <- false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if term.isListening {
|
||||||
|
ctrlc <- false
|
||||||
|
term.buffer = data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return ctrlc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (term *Terminal) listen() ([]byte, error) {
|
||||||
|
buffer := make([]byte, 512)
|
||||||
|
n, err := term.channel.Read(buffer)
|
||||||
|
return buffer[:n], err
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTerminal(channel ssh.Channel) *Terminal {
|
||||||
|
return &Terminal{
|
||||||
|
channel: channel,
|
||||||
|
|
||||||
|
lock: &sync.Mutex{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
41
sshoneypot/logging.go
Normal file
41
sshoneypot/logging.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package sshoneypot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A simple high changeable logging interface
|
||||||
|
type Logging struct {
|
||||||
|
enable bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logging) Info(content string) {
|
||||||
|
if l.enable {
|
||||||
|
log.Println(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logging) Infof(format string, args... interface{}) {
|
||||||
|
log.Printf(fmt.Sprintf(format, args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logging) Warn(content string) {
|
||||||
|
if l.enable {
|
||||||
|
log.Println(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logging) Warnf(format string, args... interface{}) {
|
||||||
|
log.Printf(fmt.Sprintf(format, args))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logging) Error(content string) {
|
||||||
|
if l.enable {
|
||||||
|
log.Println(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logging) Errorf(format string, args... interface{}) {
|
||||||
|
l.Error(fmt.Sprintf(format, args))
|
||||||
|
}
|
45
sshoneypot/rsa.go
Normal file
45
sshoneypot/rsa.go
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
package sshoneypot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"io/ioutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoadSSHKey loads a ssh key out of bytes
|
||||||
|
func LoadSSHKey(keyBytes []byte) (ssh.Signer, error) {
|
||||||
|
// parse the read key bytes
|
||||||
|
key, err := ssh.ParsePrivateKey(keyBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return key, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadSSHKeyFromFile loads a ssh key out of a file
|
||||||
|
func LoadSSHKeyFromFile(file string) (ssh.Signer, error) {
|
||||||
|
keyBytes, err := ioutil.ReadFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return LoadSSHKey(keyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateSSHKey generates a new rsa ssh key
|
||||||
|
func GenerateSSHKey() ([]byte, error) {
|
||||||
|
// create a key
|
||||||
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
privateKeyBlock := pem.Block{
|
||||||
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Headers: nil,
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(&privateKeyBlock), nil
|
||||||
|
}
|
362
sshoneypot/sshoneypot.go
Normal file
362
sshoneypot/sshoneypot.go
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
package sshoneypot
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/handler"
|
||||||
|
"github.com/bytedream/sshoneypot/sshoneypot/info"
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
"golang.org/x/crypto/ssh/terminal"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const keyTab = 9
|
||||||
|
|
||||||
|
type SSHoneypot struct {
|
||||||
|
AllowedUsers []string
|
||||||
|
AllowedPasswords []string
|
||||||
|
|
||||||
|
Panic bool
|
||||||
|
Logger *Logging
|
||||||
|
Handler map[string]info.Handler
|
||||||
|
FS *info.FileSystem
|
||||||
|
|
||||||
|
Port uint16
|
||||||
|
ServerConfig *ssh.ServerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (honeypot SSHoneypot) AddHandler(name string, handler info.Handler, aliases... string) error {
|
||||||
|
for _, name := range append([]string{name}, aliases...) {
|
||||||
|
if _, exist := honeypot.Handler[name]; exist {
|
||||||
|
return errors.New("The handler name '" + name + "' already exists")
|
||||||
|
}
|
||||||
|
honeypot.Handler[name] = handler
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerFunc func(info *info.Info)
|
||||||
|
|
||||||
|
//simpleHandler is a easy implementation of Handler / SimpleHandler for the AddHandlerFunc method below
|
||||||
|
type simpleHandler struct {
|
||||||
|
info.Handler
|
||||||
|
|
||||||
|
HandlerName string
|
||||||
|
handleFunc HandlerFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sh simpleHandler) AutoCompleteOptions() []string {
|
||||||
|
return make([]string, 0)
|
||||||
|
}
|
||||||
|
func (sh simpleHandler) AutoCompleteRequest(info *info.Info) (accepted bool) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func (sh simpleHandler) Handle(info *info.Info) {
|
||||||
|
sh.handleFunc(info)
|
||||||
|
}
|
||||||
|
func (sh simpleHandler) Kill() {}
|
||||||
|
|
||||||
|
func (honeypot SSHoneypot) AddHandlerFunc(name string, handler HandlerFunc, aliases... string) error {
|
||||||
|
return honeypot.AddHandler(name, simpleHandler{handleFunc: handler}, aliases...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve starts the ssh server and serve it until the program ends
|
||||||
|
func (honeypot SSHoneypot) Serve() error {
|
||||||
|
// starts the ssh listener
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", honeypot.Port))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer listener.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
honeypot.Logger.Warnf("Failed to accept new client %s: %s\n", conn.RemoteAddr(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
go honeypot.handleConn(conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (honeypot SSHoneypot) handleConn(conn net.Conn) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
serverConn, channels, requests, err := ssh.NewServerConn(conn, honeypot.ServerConfig)
|
||||||
|
if err != nil {
|
||||||
|
honeypot.Logger.Warnf("Failed to create new connection with %s: %s\n", conn.RemoteAddr(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
term := &info.Termina{}
|
||||||
|
go handleRequest(requests, term)
|
||||||
|
for channel := range channels {
|
||||||
|
go honeypot.handleChannel(conn, channel, serverConn, term)
|
||||||
|
}
|
||||||
|
honeypot.Logger.Infof("%s closed the connection\n", conn.RemoteAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (honeypot SSHoneypot) handleChannel(conn net.Conn, newChannel ssh.NewChannel, serverConn *ssh.ServerConn, term *info.Termina) {
|
||||||
|
channel, request, err := newChannel.Accept()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
info.NewTerminal(channel).ReadLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
term.Terminal = terminal.NewTerminal(channel, "")
|
||||||
|
term.SetPrompt(fmt.Sprintf("%s:%s $ ", serverConn.User(), "/"))
|
||||||
|
term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
|
||||||
|
if key == keyTab {
|
||||||
|
if command, err := parseCommand(line); len(command) > 0 && err == nil {
|
||||||
|
if cmd, exists := honeypot.Handler[command[0]]; exists {
|
||||||
|
cmdInfo := &info.Info{Conn: conn, Terminal: term, FS: honeypot.FS, User: serverConn, FullArgs: command, Command: command[0], Args: command[1:], Stdout: true, Stderr: true}
|
||||||
|
if cmd.AutoCompleteRequest != nil && !cmd.AutoCompleteRequest(cmdInfo){
|
||||||
|
if options := cmd.AutoCompleteOptions(); options != nil && len(options) == 0 {
|
||||||
|
newLine, newPos, ok = honeypot.fileAutoComplete(cmdInfo, line)
|
||||||
|
} else if len(options) == 1 {
|
||||||
|
newLine = line + options[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go handleRequest(request, term)
|
||||||
|
for {
|
||||||
|
line, err := term.ReadLine()
|
||||||
|
if err != nil {
|
||||||
|
if err == io.EOF {
|
||||||
|
if honeypot.OnClose(channel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Println(line)
|
||||||
|
/*if !honeypot.handleLine(line, conn, channel, serverConn, term) {
|
||||||
|
if honeypot.OnClose(channel) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (honeypot SSHoneypot) handleLine(line string, conn net.Conn, channel ssh.Channel, serverConn *ssh.ServerConn, term *info.Termina) string {
|
||||||
|
command, err := parseCommand(line)
|
||||||
|
if err == nil && len(command) > 0 {
|
||||||
|
clientInfo := &info.Info{Conn: conn, Terminal: term, FS: honeypot.FS, User: serverConn, FullArgs: command, Command: command[0], Args: command[1:], Stdout: true, Stderr: true}
|
||||||
|
var redirect bool
|
||||||
|
for i, arg := range clientInfo.Args {
|
||||||
|
if redirect {
|
||||||
|
redirect = false
|
||||||
|
clientInfo.Args = append(clientInfo.Args[:i], clientInfo.Args[i+1:]...)
|
||||||
|
}
|
||||||
|
switch arg {
|
||||||
|
case "2>", "&>":
|
||||||
|
clientInfo.Stderr = false
|
||||||
|
fallthrough
|
||||||
|
case ">", "1>", ">>":
|
||||||
|
clientInfo.Stdout = false
|
||||||
|
|
||||||
|
clientInfo.Args = append(clientInfo.Args[:i], clientInfo.Args[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if redirect {
|
||||||
|
clientInfo.Errorln("-bash: syntax error near unexpected token 'newline'")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
finished := make(chan bool, 1)
|
||||||
|
newLine := ""
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var buffer = make([]byte, 512)
|
||||||
|
n, _ := channel.Read(buffer)
|
||||||
|
select {
|
||||||
|
case <- finished:
|
||||||
|
newLine = string(buffer[:n])
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
if buffer[0] == 3 {
|
||||||
|
clientInfo.Stderr = false
|
||||||
|
clientInfo.Stdout = false
|
||||||
|
finished <- true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if commandHandler, ok := honeypot.Handler[command[0]]; ok {
|
||||||
|
if honeypot.Panic {
|
||||||
|
commandHandler.Handle(clientInfo)
|
||||||
|
} else {
|
||||||
|
func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
buffer := make([]byte, 2048)
|
||||||
|
written := runtime.Stack(buffer, false)
|
||||||
|
honeypot.Logger.Warnf("Unexpected panic: %s\n%s", r, buffer[:written])
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
commandHandler.Handle(clientInfo)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// the following part is the implementation of ctrl + c / ctrl + d when a process is running.
|
||||||
|
// sadly the ssh library doesn't support ctrl + c / some other keys to handle their input directly,
|
||||||
|
// so a second empty terminal has to be opened.
|
||||||
|
// and because of this ugly implementation, the key up / down buttons to get the recent entered command are not working :/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return newLine
|
||||||
|
} else {
|
||||||
|
handler.Default(clientInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (honeypot SSHoneypot) fileAutoComplete(cmdInfo *info.Info, line string) (newLine string, newPos int, ok bool) {
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
if len(cmdInfo.Args) > 0 {
|
||||||
|
var dir info.Directory
|
||||||
|
search := cmdInfo.Args[len(cmdInfo.Args) - 1]
|
||||||
|
|
||||||
|
if strings.HasPrefix(search, string(os.PathSeparator)) {
|
||||||
|
dir, _ = honeypot.FS.GetExplicitDirectory("/", false)
|
||||||
|
} else {
|
||||||
|
dir = honeypot.FS.Cwd()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range dir.Files {
|
||||||
|
if strings.HasPrefix(file.Name, search) && file.Name != search {
|
||||||
|
names = append(names, file.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(names) == 1 && strings.HasPrefix(search, string(os.PathSeparator)) {
|
||||||
|
names[0] = filepath.Join(string(os.PathSeparator), names[0])
|
||||||
|
} else if len(names) == 0 && strings.Contains(search, string(os.PathSeparator)) {
|
||||||
|
parent, _ := filepath.Split(search)
|
||||||
|
if dir, ok := honeypot.FS.GetExplicitDirectory(parent, false); ok {
|
||||||
|
for _, file := range dir.Files {
|
||||||
|
if strings.HasPrefix(filepath.Join(parent, file.Name), search) && file.Name != search {
|
||||||
|
names = append(names, file.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(names) == 1 {
|
||||||
|
names[0] = filepath.Join(parent, names[0]) + string(os.PathSeparator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
|
||||||
|
dir := honeypot.FS.Cwd()
|
||||||
|
for _, file := range dir.Files {
|
||||||
|
names = append(names, file.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(names) == 0 {
|
||||||
|
return
|
||||||
|
} else if len(names) == 1 {
|
||||||
|
ok = true
|
||||||
|
if len(cmdInfo.Args) == 0 {
|
||||||
|
newLine += names[0]
|
||||||
|
} else {
|
||||||
|
newLine = line[:strings.LastIndex(line, " ")] + " " + names[0]
|
||||||
|
}
|
||||||
|
newPos = len(newLine)
|
||||||
|
} else {
|
||||||
|
honeypot.OptionsAutoComplete(names, cmdInfo, line)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// OptionsAutoComplete will show all given options in the console.
|
||||||
|
// e.g. all files in the current directory when double pressing `tab` after the `ls` command
|
||||||
|
func (honeypot SSHoneypot) OptionsAutoComplete(options []string, info *info.Info, line string) {
|
||||||
|
sort.Strings(options)
|
||||||
|
|
||||||
|
var length int
|
||||||
|
for _, option := range options {
|
||||||
|
if l := len(option); length < l {
|
||||||
|
length = l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
length++
|
||||||
|
|
||||||
|
var builder strings.Builder
|
||||||
|
var perline int
|
||||||
|
if info.Terminal.Width > 0 {
|
||||||
|
perline = int(info.Terminal.Width) / length
|
||||||
|
} else {
|
||||||
|
perline = 80 / length
|
||||||
|
}
|
||||||
|
builder.WriteString(info.Terminal.Prompt + line + "\n")
|
||||||
|
for i, option := range options {
|
||||||
|
if i % perline == 0 && i != 0 {
|
||||||
|
builder.WriteString("\n")
|
||||||
|
}
|
||||||
|
builder.WriteString(option + strings.Repeat(" ", length - len(option)))
|
||||||
|
}
|
||||||
|
builder.WriteString("\n")
|
||||||
|
|
||||||
|
info.Write(builder.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- The following methods were created to allow user to manipulate the ssh process as they wish --- //
|
||||||
|
|
||||||
|
// OnClose gets called when the client send an exit signal.
|
||||||
|
// If the return bool is true, the channel will be closed an the client disconnected
|
||||||
|
func (honeypot SSHoneypot) OnClose(channel ssh.Channel) (close bool) {
|
||||||
|
channel.SendRequest("exit-status", false, ssh.Marshal(struct {Status uint32}{Status: 0}))
|
||||||
|
channel.Close()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- //
|
||||||
|
|
||||||
|
func DefaultSSHoneypot(fs *info.FileSystem, key ssh.Signer) SSHoneypot {
|
||||||
|
serverConfig := &ssh.ServerConfig{
|
||||||
|
PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
|
||||||
|
return nil, nil
|
||||||
|
},
|
||||||
|
ServerVersion: "SSH-2.0-OpenSSH_8.5",
|
||||||
|
}
|
||||||
|
serverConfig.AddHostKey(key)
|
||||||
|
|
||||||
|
honeypot := SSHoneypot{
|
||||||
|
Logger: &Logging{enable: true},
|
||||||
|
Port: 2222,
|
||||||
|
ServerConfig: serverConfig,
|
||||||
|
|
||||||
|
Handler: map[string]info.Handler{},
|
||||||
|
|
||||||
|
FS: fs,
|
||||||
|
}
|
||||||
|
honeypot.AddHandler("cat", handler.Cat{}, "/bin/cat")
|
||||||
|
honeypot.AddHandler("cd", handler.CD{})
|
||||||
|
honeypot.AddHandler("ls", handler.LS{}, "/bin/ls")
|
||||||
|
honeypot.AddHandler("mkdir", handler.Mkdir{}, "/bin/mkdir")
|
||||||
|
honeypot.AddHandler("rm", handler.RM{}, "/bin/rm")
|
||||||
|
|
||||||
|
honeypot.AddHandlerFunc("clear", handler.Clear, "/bin/clear")
|
||||||
|
honeypot.AddHandlerFunc("echo", handler.Echo, "/bin/echo")
|
||||||
|
|
||||||
|
honeypot.AddHandlerFunc("ifconfig", handler.Ifconfig, "/sbin/ifconfig")
|
||||||
|
honeypot.AddHandler("ping", handler.Ping{}, "/bin/ping", "/bin/ping4", "/bin/ping6")
|
||||||
|
|
||||||
|
return honeypot
|
||||||
|
}
|
Reference in New Issue
Block a user