Repository: cowrie/cowrie

Issue:
Adding command 'uniq'
**Is your feature request related to a problem? Please describe.**
From an attack the following command was issued:
CMD: grep 'cpu cores' /proc/cpuinfo | uniq

**Describe the solution you'd like**
The command "uniq" does not exist yet. From the man pages:
Filter adjacent matching lines from INPUT (or standard input), writing to OUTPUT (or standard output).
With no options, matching lines are merged to the first occurrence.

**Describe alternatives you've considered**
Unsure if there is an alternative.

**Additional context**
No additional context


Discussion:

Resolving patch:
diff --git a/src/cowrie/commands/__init__.py b/src/cowrie/commands/__init__.py
--- a/src/cowrie/commands/__init__.py
+++ b/src/cowrie/commands/__init__.py
@@ -40,6 +40,7 @@
     'tftp',
     'ulimit',
     'uname',
+    'uniq',
     'uptime',
     'wc',
     'wget',
diff --git a/src/cowrie/commands/uniq.py b/src/cowrie/commands/uniq.py
new file mode 100644
--- /dev/null
+++ b/src/cowrie/commands/uniq.py
@@ -0,0 +1,52 @@
+# Copyright (c) 2020 Peter Sufliarsky <sufliarskyp@gmail.com>
+# See the COPYRIGHT file for more information
+
+"""
+uniq command
+"""
+
+from __future__ import absolute_import, division
+
+from twisted.python import log
+
+from cowrie.shell.command import HoneyPotCommand
+
+
+commands = {}
+
+
+class command_uniq(HoneyPotCommand):
+
+    unique_lines = set()
+
+    def start(self):
+        if self.input_data:
+            lines = self.input_data.split(b'\n')
+            if not lines[-1]:
+                lines.pop()
+
+            self.unique_lines = set(lines)
+            for line in self.unique_lines:
+                self.writeBytes(line + b'\n')
+
+            self.exit()
+
+    def lineReceived(self, line):
+        log.msg(eventid='cowrie.command.input',
+                realm='uniq',
+                input=line,
+                format='INPUT (%(realm)s): %(input)s')
+
+        self.grep_input(line)
+
+    def handle_CTRL_D(self):
+        self.exit()
+
+    def grep_input(self, line):
+        if line not in self.unique_lines:
+            self.writeBytes(line.encode() + b'\n')
+            self.unique_lines.add(line)
+
+
+commands['/usr/bin/uniq'] = command_uniq
+commands['uniq'] = command_uniq


Test patch:
diff --git a/src/cowrie/test/test_uniq.py b/src/cowrie/test/test_uniq.py
new file mode 100644
--- /dev/null
+++ b/src/cowrie/test/test_uniq.py
@@ -0,0 +1,60 @@
+# -*- test-case-name: Cowrie Test Cases -*-
+
+# Copyright (c) 2020 Peter Sufliarsky
+# See LICENSE for details.
+
+"""
+Tests for uniq command
+"""
+
+from __future__ import absolute_import, division
+
+import os
+
+from twisted.trial import unittest
+
+from cowrie.shell import protocol
+from cowrie.test import fake_server, fake_transport
+
+os.environ["HONEYPOT_DATA_PATH"] = "../data"
+os.environ["HONEYPOT_DOWNLOAD_PATH"] = "/tmp"
+os.environ["SHELL_FILESYSTEM"] = "../share/cowrie/fs.pickle"
+
+PROMPT = b"root@unitTest:~# "
+
+
+class ShellUniqCommandTests(unittest.TestCase):
+
+    def setUp(self):
+        self.proto = protocol.HoneyPotInteractiveProtocol(fake_server.FakeAvatar(fake_server.FakeServer()))
+        self.tr = fake_transport.FakeTransport("1.1.1.1", "1111")
+        self.proto.makeConnection(self.tr)
+        self.tr.clear()
+
+    def test_uniq_command_001(self):
+        """
+        echo test | uniq
+        """
+        self.proto.lineReceived(b'echo test | uniq\n')
+        self.assertEquals(self.tr.value(), b'test\n' + PROMPT)
+
+    def test_uniq_command_002(self):
+        """
+        echo -e "test\ntest\ntest" | uniq
+        """
+        self.proto.lineReceived(b'echo -e "test\ntest\ntest" | uniq\n')
+        self.assertEquals(self.tr.value(), b'test\n' + PROMPT)
+
+    def test_uniq_command_003(self):
+        """
+        test without arguments, read stdin and quit after Ctrl+D
+        """
+        self.proto.lineReceived(b'uniq\n')
+        self.proto.lineReceived(b'test\n')
+        self.proto.lineReceived(b'test\n')
+        self.proto.lineReceived(b'test\n')
+        self.proto.handle_CTRL_D()
+        self.assertEquals(self.tr.value(), b'test\n\n' + PROMPT)
+
+    def tearDown(self):
+        self.proto.connectionLost("tearDown From Unit Test")