SVN auf DEBIAN 9 (Stretch) erstellen

Installation und Einrichten von SVN auf DEBIAN 9

Zunächst das Paket SVN installieren:

apt-get install svn
apt-get install libapache2-mod-svn subversion-tools

Dann erstellen wir ein leeres Repository:

mkdir /var/lib/svn

svnadmin create /var/lib/svn

Noch den Zugriff für Apache einrichten:

chown -R www-data:www-data /var/lib/svn/

Zugang mit User/Passwort konfigurieren:

Der erste User:

htpasswd -cm /etc/apache2/dav_svn.passwd admin

jeder weitere:

htpasswd -m /etc/apache2/dav_svn.passwd stduser

Die Zugriffsart konfigurieren:

vim /etc/apache2/dav_svn.authz

Inhalt:

[/]
* = r
admin = rw
stduser = rw

Subversion Daemon starten:

# svnserve -d -r /var/lib/svn/

Optional die Apache Module aktivieren

# a2enmod dav
# a2enmod dav_svn
# a2enmod authz_svn

Damit die Zugriffe auch über http (bzw. https) erfolgen können muss noch die Datei /etc/apache2/mods-available/dav_svn.conf wie folgt angepasst werden:

DAV svn
SVNPath /var/lib/svn
AuthType Basic
AuthName „Subversion Repository“
AuthUserFile /etc/apache2/dav_svn.passwd
AuthzSVNAccessFile /etc/apache2/dav_svn.authz
Require valid-user
## zum Aktivieren von SSL
# SSLRequireSSL

 

Apache neu starten

#$ /etc/init.d/apache2 force-reload

Die URL lautet dann
http://yourserverip/svn/

Test an der Console:

#$ cd SVNrepo1
$ touch test.txt
#$ echo HELLO >test.txt
#$ svn add test.txt
>A test.txt

svn commit -m ‚First Revision‘
Authentication realm: <http://localhost:1080> Subversion Repository

In eclipse über die Perspective „SVN Repository Exploring“ auschecken.

Soundex in deutsch

Die hervorragende Funktion SOUNDEX für phonetische Ähnlichkeit ist auf die englische Sprache optimiert.
Es gibt jedoch auch diverse Quellen für eine deutsche Implemantation.

Hier jetzt zunächst nur die Verweise:

https://www.doag.org/formes/pubfiles/5428921/20140128-regio-fr-meisriemler-Koelner_Phonetik.pdf

Teaser für diesen Link der DOAG:

select distinct NAME1,
SYS.UTL_MATCH.EDIT_DISTANCE(‚MEIER‘, NAME1) Distanz
from tab_kunden
where func_phonetik(name1) = func_phonetik(‚MEIER‘)
order by 2 desc, 1

https://github.com/deezaster/germanphonetic

Nicht direkt Oracle aber viele Details:

German Soundex „Kölner Phonetik“ SQL implementation

NIMSOFT Tabellen RN_QOS_DATA_nnnn laufen voll, Archivierung versagte

Die Archivierung der NIMSOFT Daten auf dem zentralen Server stand. Der Server sammelt alle Daten aller Maschinen.

Allein eine Schleife mit einem count(*)  über die Datentabellen lief den ganzen Vormittag.   Zunächst die Ermittlung des Istzustandes.

Ich startete mit :

1
2
QUERYL := 'select COUNT(1) LINES FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA;

Verglichen wird gegen trunc(SYSDATE), nicht gegen SYSDATE, damit der evaluierte Ausdruck auch bei Wiederholung konstant bleibt, was bei dem puren SYSDATE nicht der Fall ist (Uhrzeitanteil würde sich ändern).  Könnte Cache-Benefit liefern. Theoretisch könnte man es auch als Bind-Variable hinschreiben.

Aber man kann die Performance noch erheblich steigern. Ähnlich wie bei einem „WHERE EXISTS“, wenn man nur wissen will ob etwas da ist, hilft hier statt dem Count auf die volle Ergebnismenge der Count auf die „limitierte Mindestmenge“. Da wir in erster Näherung resourcenschonend in 200000-er Blöcken Löschen wollten, konnte ich die Laufzeiten etwas verbessern mit

1
2
3
4
-- nicht direkt zählen nur "ist mindestens der zu löschende Block da"
QUERYL := 'SELECT COUNT(1) FROM (SELECT 1 FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA
|| ' AND ROWNUM <= ' || MAXROWNUM || ')';

So konnte man schon mal die Laufzeit der Ermittlung von „ganzem Vormittag“ auf einige Minuten drücken. 

Letzte Hürde:

Ungefähr drei Tabellen wehrten sich noch, mit Laufzeiten von bis zu sechs Minuten für eine de  Facto leere Tabelle ohne Daten. Woran kann das liegen?

BN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:46:56
DN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:46:56
HN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:46:56
RN_QOS_DATA_0041 NumRows: 0 TODELETE 0
29.11.2017 11:53:01
BN_QOS_DATA_0042 NumRows: 0 TODELETE 0
29.11.2017 11:53:01

Struktur der Tabelle validiert. Tabelle reorganisiert (MOVE). Keine Auswirkung.

Index geprüft. Er ist nicht „unusable“.

Trotzdem war die Lösung ein „Alter Index REBUILD“.
Das neu Erzeugen des Index eliminierte das Performanceproblem.
Nachdem das Script noch um die Ausgabe der Deltatime pro Tabellenanalyse ergänzt wurden, fielen noch 2-3 weitere Kandidaten auf, deren Probleme genauso gelöst werden konnten. Eventuell hätte auch eine Suche nach  Indizes mit hohem Fragmentierungsgrad zum Ziel geführt, das könnte die Ursache gewesen sein. Habe daher auch schon bei Kunden für solche Fälle einen automatisierten Indexrebuild in der Wartung implementiert.

Ein Script löst dann die Probleme mit den aus dem Ruder gelaufenen Datenmengen ( > 900 Tage angefallen).

Folgende Zeile

RN_QOS_DATA_0001 NumRows: 36871127 TODELETE 200000

bedeutet, das in der Tabelle RN_QOS_DATA_0001 mindestens 200000 Zeilen zu löschen sind, die älter als 900 Tage sind.
In der nächsten Zeile haben wir ein Beispiel, wo die Tabelle nach der ersten Iteration schon clean ist, da nur 8815 Rows anfallen.

RN_QOS_DATA_0024 NumRows: 8815 TODELETE 8815

Kompletter Report:

Database = DB DELTA 900 -> DATUMSSCHWELLE 13-06-2015 READONLY1
BN_QOS_DATA_0001 NumRows: 0 TODELETE 0
29.11.2017 15:29:36 delta t sec 0
DN_QOS_DATA_0001 NumRows: 0 TODELETE 0
29.11.2017 15:29:36 delta t sec 0
HN_QOS_DATA_0001 NumRows: 0 TODELETE 0
29.11.2017 15:29:36 delta t sec 0
RN_QOS_DATA_0001 NumRows: 36871127 TODELETE 200000
<===============
29.11.2017 15:29:37 delta t sec ,72
BN_QOS_DATA_0002 NumRows: 0 TODELETE 0
29.11.2017 15:29:37 delta t sec 0
DN_QOS_DATA_0002 NumRows: 0 TODELETE 0
29.11.2017 15:29:37 delta t sec 0

RN_QOS_DATA_0462 NumRows: 11992679 TODELETE 0
29.11.2017 15:29:54 delta t sec 0
CLONE_RN_QOS_DATA_0041 NumRows: TODELETE 0
29.11.2017 15:29:54 delta t sec 0
585 TABELLEN betrachtet
================> LÖSCHUNG IST ERFORDERLICH
Ein Löschbefehl:
DELETE FROM NMUSER.CLONE_RN_QOS_DATA_0041 WHERE TABLE_ID >= 0 AND SAMPLETIME < TRUNC(SYSDATE) – 900 AND ROWNUM <= 200000

Das Script das die Löschungen resourcenschonend durchführt:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
DECLARE
/* Programmparameter = Konstanten zur Steuerung des Laufzeitverhaltens */
/* Eine Iteration dauert so knapp über 30 min , 4 -> ~2h */
MAXITER CONSTANT NUMBER := 4; zum Limitieren der Laufzeit nutzen
MAXROWNUM CONSTANT NUMBER := 200000; -- adaptieren ; jetzt ca 30 Minuten/iter
DELTA CONSTANT NUMBER := 900; -- DELTA Tage zurück!
USERNAM CONSTANT VARCHAR2(6) := 'NMUSER'; -- der Tableowner

CURSOR NIM_TABLES IS
SELECT table_name, num_rows
FROM dba_tables
WHERE OWNER = USERNAM
AND table_name LIKE '%N_QOS_DATA_%'
ORDER BY SUBSTR(table_name,11),SUBSTR(table_name,1,1);

QUERYL VARCHAR2(500);
D_DML VARCHAR2(500);
TOKEN VARCHAR(32);
LINES NUMBER;
TODELETE NUMBER; -- Löschung erforderlich
GLOBTODELETE NUMBER; -- GLOBALES TODELETE
DELDONE NUMBER; -- hat gelöscht?
NITER NUMBER; -- Anzahl Iterationen
TBD NUMBER; -- noch was zu tun?
RERUN_HINT NUMBER;
EXAMINE_ONLY NUMBER; -- READONLY J/N
POSSIBLE_LIMIT NUMBER;
NLOOP NUMBER;
BTABLE BOOLEAN;
V_DBNAME VARCHAR2(132);
V_DBID NUMBER;
runtime DATE;
runseconds NUMBER;
timesnap DATE;

N_ROWS DBA_TABLES.NUM_ROWS%TYPE;
NTAB_NAME DBA_TABLES.TABLE_NAME%TYPE;

DLIMIT DATE;

 

BEGIN
EXAMINE_ONLY := &&NODELETE;
--EXAMINE_ONLY := 1;
/* 0 ==> FALSE; 1 ==> TRUE */
DELDONE := 0; TBD := 0; RERUN_HINT := 0; POSSIBLE_LIMIT := 1;
DLIMIT := SYSDATE-DELTA;
GLOBTODELETE := 0;
SELECT NAME,DBID INTO V_DBNAME,V_DBID FROM V$DATABASE;

DBMS_OUTPUT.PUT_LINE('Database = '||V_DBNAME || ' DELTA ' || DELTA ||
' -> DATUMSSCHWELLE ' || TO_CHAR(DLIMIT, 'DD-MM-YYYY ') || ' READONLY' || EXAMINE_ONLY );

NLOOP := 0;
-- Hauptschleife über die NIMTABLES
OPEN NIM_TABLES;
LOOP
FETCH NIM_TABLES INTO
NTAB_NAME, N_ROWS;
EXIT WHEN NIM_TABLES%NOTFOUND;

NLOOP := NLOOP + 1;
LINES := 0;

BTABLE := SUBSTR(NTAB_NAME,1,1) = 'B';

IF NOT BTABLE THEN
TOKEN := 'SAMPLETIME';
ELSE
TOKEN := 'STOPTIME';
END IF;

timesnap := SYSDATE;

-- nicht zählen nur "ist mindestens der zu löschende Block da"
QUERYL := 'SELECT COUNT(1) FROM (SELECT 1 FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA
|| ' AND ROWNUM <= ' || MAXROWNUM || ')'; -- DBMS_OUTPUT.PUT_LINE(QUERYL); EXECUTE IMMEDIATE QUERYL INTO LINES; DBMS_OUTPUT.PUT_LINE(NTAB_NAME || ' NumRows: ' || N_ROWS || ' TODELETE '||LINES); IF LINES > 0 THEN
TODELETE := 1;
GLOBTODELETE := 1;
DBMS_OUTPUT.PUT_LINE(' <=============== '); ELSE TODELETE := 0; END IF; IF LINES >= MAXROWNUM THEN
RERUN_HINT := 1;
END IF;

DBMS_OUTPUT.PUT( TO_CHAR(SYSDATE, 'DD.MM.YYYY hh24:mi:ss') );
runseconds := 24 * 2600 * ( SYSDATE - timesnap);
DBMS_OUTPUT.put( ' delta t sec '|| TO_CHAR( runseconds , '9999.9') );
IF runseconds > 1 THEN
DBMS_OUTPUT.PUT_LINE ( ' MUCH PERFORMED ***** ');
ELSE
DBMS_OUTPUT.PUT_LINE ( ' ');
END IF;
timesnap := SYSDATE;

IF TODELETE > 0 THEN
-- Löschbefehl aufbereiten
-- Loop über Löschblöcke Designentscheidung. Inner Loop Block oder Table?
D_DML := 'DELETE FROM ' || USERNAM || '.' || NTAB_NAME
|| ' WHERE TABLE_ID >= 0 AND ' || TOKEN || ' < TRUNC(SYSDATE) - ' || DELTA
|| ' AND ROWNUM <= ' || MAXROWNUM;

IF EXAMINE_ONLY <> 0 THEN
DBMS_OUTPUT.PUT_LINE(' READONLY ');
--NULL;
ELSE

NITER := 0;
-- INNER LOOP - ITERATE Nx MAXROWNUM
WHILE TODELETE > 0 AND NITER < MAXITER LOOP IF NITER = 0 THEN DBMS_OUTPUT.PUT(' EXEC: ' ); DBMS_OUTPUT.PUT_LINE(' SHOULD DELETE CMD: ' || D_DML); END IF; EXECUTE IMMEDIATE D_DML; COMMIT; DELDONE := 1; DBMS_OUTPUT.PUT_LINE('Iteration ' || NITER || ' Es wurden '|| LINES || ' Zeilen gelöscht '); EXECUTE IMMEDIATE QUERYL INTO LINES; IF LINES > 0 THEN
TODELETE := 1;
ELSE
TODELETE := 0;
END IF;

IF LINES >= MAXROWNUM THEN
RERUN_HINT := 1;
ELSE
RERUN_HINT := 0;
END IF;

NITER := NITER +1;

END LOOP;
NULL;
END IF;

END IF; -- TODELETE

-- LIMIT FÜR TESTLAUF
--EXIT WHEN NLOOP >= 5;

END LOOP; -- NUM_TABLES;
CLOSE NIM_TABLES;

DBMS_OUTPUT.PUT_LINE(NLOOP || ' TABELLEN betrachtet ');

IF GLOBTODELETE > 0 THEN

IF DELDONE > 0 THEN
DBMS_OUTPUT.PUT_LINE(' Es wurde gelöscht ');
IF RERUN_HINT > 0 THEN
DBMS_OUTPUT.PUT_LINE(' Bitte wiederhole Aufruf, limitierter Lauf wegen Resourcenlimit ');
END IF;
ELSE
DBMS_OUTPUT.PUT_LINE(' ================> LÖSCHUNG IST ERFORDERLICH ');
DBMS_OUTPUT.PUT_LINE('Letzter exemplarischer Befehl: ');
DBMS_OUTPUT.PUT_LINE(D_DML);
END IF;

ELSE
DBMS_OUTPUT.PUT_LINE('Es war keine Löschung erforderlich ');
END IF;

EXCEPTION WHEN OTHERS THEN
IF SQLCODE = -12801 THEN
DBMS_OUTPUT.PUT_LINE (' TEMP Segment zu klein? Empfohlen ist mind. 600MB');
END IF;
DBMS_OUTPUT.PUT_LINE ('Abbruch -> Errcode ' || SQLCODE);
END;

Es folgt dann noch ein wesentlicher Schritt. Wie auch bei der SYSAUX-Bereinigung ist ein entscheidender Schritt der Reorg der indizes, die über die Jahre einen Fragmentierungsgrad von bis zu 85% erreicht haben.
Dies verbraucht viel Platz sowie Performance, daher ist nach dem Löschen der Rebuild der Indizes nötig. Glücklicherweise gibt es in der Enterprise Version einen Qualifier REBUILD ONLINE, damit ist im Rebuild die Tabelle nicht gelockt, kann also während des Betriebs erfolgen. Der Rebuild der ersten drei Indizes bracht schon 7GB Freespace.

produktives Script Online Rebuild :

(wird folgen) 

fail2ban macht den Server sicherer – speziell ssh und auch dovecot auf DEBIAN STRETCH

Fail2ban dient dazu, ständige Einlogversuche von fremden Rechnern auf den ssh-port zu unterbinden. Auch andere Ports können damit überwacht werden.

Nachdem sogar Fremdlogins auf den imap-port für existierende User auftauchten, stieg die empfundene Handlungsnotwendigkeit.

Beispiel: (Auszug aus /var/log/auth.log)

1
2
3
4
5
6
7
8
9
10
Dec 8 07:31:25 myserver sshd[9656]: Invalid user si from 113.120.16.165 port 51268
Dec 8 07:31:25 myserver sshd[9654]: Invalid user si from 113.120.16.165 port 51264
Dec 8 07:31:25 myserver sshd[9656]: input_userauth_request: invalid user si [preauth]
Dec 8 07:31:25 myserver sshd[9654]: input_userauth_request: invalid user si [preauth]
Dec 8 07:31:26 myserver sshd[9656]: pam_unix(sshd:auth): check pass; user unknown
Dec 8 07:31:26 myserver sshd[9656]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=113.120.16.165
Dec 8 07:31:26 myserver sshd[9654]: pam_unix(sshd:auth): check pass; user unknown
Dec 8 07:31:26 myserver sshd[9654]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=113.120.16.165
Dec 8 07:31:28 myserver sshd[9656]: Failed password for invalid user si from 113.120.16.165 port 51268 ssh2
Dec 8 07:31:28 myserver sshd[9654]: Failed password for invalid user si from 113.120.16.165 port 51264 ssh2

IP 113.120.16.165 startet einen Angriff und versucht sich über ssh einzuloggen.
Das bewirkt folgendes:

fail2ban log:

1
2
3
4
5
2017-12-08 07:31:25,911 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:25,912 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:26,285 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:26,288 fail2ban.filter [14863]: INFO [sshd] Found 113.120.16.165
2017-12-08 07:31:26,828 fail2ban.actions [14863]: NOTICE [sshd] Ban 113.120.16.165

Die IP wird in der Firewall eingetragen und gebannt. Man kontrolliert das etwa mit:

tail -40 /var/log/fail2ban.log

 

Weiter kann man es in der Firewall kontrollieren: Ein „iptables-save“ liefert unter anderem:

-A f2b-sshd -s 113.120.16.165/32 -j REJECT –reject-with icmp-port-unreachable

 

Zu konfigurieren ist unter anderem in der jail.conf

  • findtime = 600 (Zeitfenster für den Zähler Fehlversuche pro IP)
  • maxRetry = 3 (Der Ban erfolgt nach 3 Fehlversuchen)
  • banTime = 3600 (Die IP ist für eine Stunde gebannt)

Der Teil für den sshd war trivial. Problematischer war der Part für den dovecot.

Es fiel auf, dass auf Versuche fremder IPs sich als Mailuser einzuloggen, nichts bewirkten.
Kurzer Check der Sachlage ergab, DEBIAN 9 trägt die Loginversuche aus IMAP (dovecot) in der auth.log ein:

Dec 8 07:07:31 myserver:auth: pam_unix(dovecot:auth): check pass; user unknown
Dec 8 07:07:31 myserver auth: pam_unix(dovecot:auth): authentication failure; logname= uid=0 euid=0 tty=dovecot ruser=sfsb rhost=130.204.239.232

fail2ban ist aber auf die mail.warn konfiguriert.

Am schnellsten korrigiert man das durch Überschreiben der entsprechenden Konfiguration in der Konfig (jail.local)

# dovecot defaults to logging to the mail syslog facility
# but can be set by syslog_facility in the dovecot configuration.
[dovecot]
# enabled = true
port = pop3,pop3s,imap,imaps,submission,465,993,sieve
#logpath = %(dovecot_log)s
logpath = /var/log/auth.log
backend = %(dovecot_backend)s

Mit dem harten Überschreiben des logpath in der Section für dovecot ist das Problem gelöst:

2017-12-08 10:34:08,015 fail2ban.filter [28202]: INFO [dovecot] Found 218.23.49.154

 


Etwas allgemeiner Ansatz wäre, in der paths-common.conf (in /etc/fail2ban/) folgendes zu überschreiben.

dovecot_log = %(syslog_mail_warn)s

Seiteneffekte sind jedoch unklar, man findet da folgendes vor:

# There is no sensible generic defaults for syslog log targets, thus
# leaving them empty here so that no errors while parsing/interpolating configs
syslog_mail_warn =
syslog_daemon =
syslog_ftp =
syslog_local0 =

Damit würde wohl:
dovecot_log = /var/log/auth.log
auch das Problem lösen.

Weitere Informationen über die Konfigurationsmöglichkeiten von fail2ban-server erhält man mit „man fail2ban-client“.

Number of processes exceeded: zeitliche Entwicklung als Kurzübersicht

Auf einer 12c Datenbank zeigte sich ein ORA-0020

ORA-00020: maximum number of processes (300) exceeded

Bevor der Parameter erhöht wird mit

ALTER SESSION SET PROCESSES=350 scope=BOTH;

lohnt es sich zu schauen, wie denn die Entwicklung der letzten Tage (max des Values in der History) war.

Folgender SQL-Befehl leistet das:

1
2
3
4
5
6
7
8
9
10
-- Maximum per day:
SELECT instance_number inst, TO_CHAR( begin_time, 'YYYY-MM-DD') DATUM , MAX(processes) FROM
(SELECT instance_number, ROUND(maxval/100 * gv$parameter.VALUE) processes, begin_time
FROM dba_hist_sysmetric_summary
join gv$parameter
ON dba_hist_sysmetric_summary.instance_number = gv$parameter.inst_id
WHERE gv$<a href="http://parameter.name">parameter.name</a> = 'processes'
AND metric_name = 'Process Limit %')
GROUP BY instance_number,TO_CHAR( begin_time, 'YYYY-MM-DD')
ORDER BY DATUM DESC;

Ausgabe:

INST DATUM MAX(PROCESSES)
 ---------- ---------- --------------
 1 2017-11-27 299
 1 2017-11-26 265
 1 2017-11-25 268
 1 2017-11-24 265
 1 2017-11-23 218
 1 2017-11-22 219
 1 2017-11-21 218
 1 2017-11-20 137
 1 2017-11-19 137
 1 2017-11-18 134

Nun kann man anhand der Entwicklung entscheiden, ob man auf jeden Fall den INIT.ORA Parameter überhöht, oder ob man es als einzelen Außreisser wertet. Selbst dann müsste man entscheiden ob man nicht dennoch erhöht.
Jedoch hier spricht die stetig steigende Resourcenauslastung, wenn keine weitere Ursachenforschung sinnvoll erscheint, für die pragmatische Erhöhung zu sprechen.

ORACLE: Passwort = Username, unsichere Passwörter ermitteln

 unsicheres Passwort, identisch mit Username oder „meistgenutzt“ (auf Liste)
Sicherheitslücke: User mit leicht zu erratenden Passwort

Ein weiterer Aspekt, der es potentiellen Angreifern leichter macht ist der Fall, dass ein User sein Passwort als Usernamen gewählt hat.

Wir stellen also ein einfaches Script vor, das die User ermittelt die in diese  Sicherheitsklasse fallen.

Wir gehen von der Annahme aus, der User hätte Username = Passwort.

Schritte zu unserer Lösung:
  1. Loopen über alle DB-User
  2. hashen unseres angenommen Passworts nach der Standardmethode
  3. ziehen des gespeicherten Hashs
  4. compare. Bei Treffer, Listung des Users

Um einen Testfall zu haben, machen wir folgendes, ausgehend von User SCOTT, oder einem neu anzulegenden User TEST, um einen Testfall für das Script zu haben:

ALTER USER SCOTT IDENTIFIED BY SCOTT;

 

Wir lassen unser Script laufen:

SCOTT is UNSAFE PWD guessed IS SCO******
TEST is UNSAFE PWD guessed IS TES******

PL/SQL-Prozedur erfolgreich abgeschlossen.

 

Oracle Passwörter sind jedoch nicht mehr Case-Insensitiv.
Was ist, wenn des Users Passwort „Scott“ lautet? Es sind immerhin nur wenige Versuche für den potentiellen Angreifer, die KOmbinationen SCOTT, Scott sowie scott durchzuprobieren. Wir testen das aus:

ALTER USER SCOTT IDENTIFIED BY Scott;

Ergebnis:

SCOTT is UNSAFE PWD guessed IS Sco******
TEST is UNSAFE PWD guessed IS TES******

PL/SQL-Prozedur erfolgreich abgeschlossen.

 

Unser Script findet das auch! Natürlich haben wir das schon bedacht, obige Steps 2-4 erfolgen jeweils für die Varianten „angenommenes Passwort Username“ in den beschriebenen drei Varianten.

  1. Loopen über alle DB-User
  2. Erstelle Passwort wie Username in Lowercase ( jump to 5)
  3. Erstelle Passwort wie Username in Uppercase  ( jump to 5)
  4. Erstelle Passwort wie Username in Mixedcase (Capitals) ( jump to 5)
  5. hashen unseres angenommen Passworts nach der Standardmethode
  6. ziehen des gespeicherten Hashs
  7. compare. Bei Treffer, Listung des Users.
  8. jump back – next step

Wie sieht das script aus? Hier haben wir es:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
SET serveroutput ON size 1000000

DECLARE
unsecure BOOLEAN;
DUMMY VARCHAR2(80);
c_username VARCHAR2(256);
chk_token VARCHAR2(256);
dbname v$database.name%TYPE;

lv_pwd_raw RAW(128);
lv_enc_raw RAW(2048);
LV_HASH_FOUND VARCHAR2(300);

CURSOR c_main(cp_user IN VARCHAR2)
IS
SELECT SUBSTR(spare4,3,40) hash,
SUBSTR(spare4,43,20) salt,
spare4
FROM sys.USER$
WHERE name=cp_user;
lv_user c_main%ROWTYPE;

CURSOR cu_user
IS
SELECT username
FROM dba_users
WHERE ACCOUNT_STATUS LIKE 'OPEN%';

FUNCTION COMPARE_PW_HASH(luser IN VARCHAR2, lpw IN VARCHAR2)
RETURN NUMBER IS

BEGIN
OPEN c_main(UPPER(luser));
FETCH c_main INTO lv_user;
CLOSE C_MAIN;
lv_pwd_raw := UTL_RAW.cast_to_raw(lpw)||HEXTORAW(lv_user.salt);
lv_enc_raw := sys.dbms_crypto.hash(lv_pwd_raw, 3);
lv_hash_found:=UTL_RAW.cast_to_varchar2(lv_enc_raw);

IF LV_ENC_RAW = LV_USER.hash THEN
-- DBMS_OUTPUT.PUT_LINE('PWD found for User ' || luser );
RETURN 1;
ELSE
-- dbms_output.put_line('PWD not found for Safe User ' || luser);
RETURN 0;
END IF;
END;

BEGIN
DBMS_OUTPUT.ENABLE(10000000);

SELECT NAME INTO dbname FROM v$database;
--DBMS_OUTPUT.PUT_LINE('Start Checking DB ' || dbname);
--DBMS_OUTPUT.PUT_LINE('');

OPEN cu_user;
LOOP
FETCH cu_user INTO c_username;
EXIT WHEN cu_user%NOTFOUND;

unsecure := FALSE;

IF COMPARE_PW_HASH ( c_username , c_username ) > 0 THEN
unsecure := TRUE;
chk_token := c_username;
ELSIF COMPARE_PW_HASH ( c_username , UPPER(c_username) ) > 0 THEN
unsecure := TRUE;
chk_token := UPPER ( c_username) ;
ELSIF COMPARE_PW_HASH ( c_username , UPPER(SUBSTR(c_username,1,1))
|| LOWER(SUBSTR(c_username,2,LENGTH(c_username)))
) > 0 THEN
unsecure := TRUE;
chk_token := UPPER(SUBSTR(c_username,1,1))
|| LOWER(SUBSTR(c_username,2,LENGTH(c_username)));
ELSIF COMPARE_PW_HASH ( c_username , '123456' ) > 0 THEN
unsecure := TRUE;
chk_token := '123456';
END IF;

IF unsecure THEN
DBMS_OUTPUT.PUT_LINE( c_username || ' is UNSAFE PWD guessed IS '
|| SUBSTR(chk_token,1,3) || '******' );
ELSE
--DBMS_OUTPUT.PUT_LINE(c_username );
NULL;
END IF;
END LOOP;
CLOSE cu_user;
--
END;
Prüfung auf beliebte, damit unsichere Passwörter

 

Jetzt verfeinern wir das Script noch um einen kleinen Aspekt. Erstaunlicherweise gibt es tatsächlich „beliebte Passwörter“.
Hier Artikel zu dem Thema:

https://www.com-magazin.de/news/sicherheit/25-unsichersten-passwoerter-2015-1072804.html

http://www.maclife.de/news/diese-passwoerter-sollten-keinen-fall-verwenden-10086942.html

Passwort Hitliste weltweit 2015:
  1. 123456
  2. password
  3. 12345678
  4. qwerty
  5. …. (siehe Link)

Das heisst, Passwörter wie „123456“ sollten wir auch nicht verwenden.
Dieses Passwort verwenden laut dieser Untersuchungen signifikant viele Personen, daher wären es für den potentiellen Angreifer auch nur einige wenige Versuche die ersten 20% Wahrscheinlichkeit des benutzten Passwortes abzuchecken. Daher nehmen wir diese in eine erweitere Variante des Scriptes auf. Die eingebaute Liste der Passwörter ist natürlich nachzupflegen. In der Luxusvariante läge sie in einer Tabelle, in der mindestens jährlich die neuesten Passworthits eingepflegt werden, uns reicht hier für das Prinzip jedoch eine fest verdrahtete Liste.

Wir testen das:

ALTER USER SCOTT IDENTIFIED BY 123456;

 

Ausgabe des neuen Scriptes:

SCOTT is UNSAFE PWSTYLE IS 4 **
TEST is UNSAFE PWSTYLE IS 1 **

PL/SQL-Prozedur erfolgreich abgeschlossen.

 

Was hat sich geändert?  Nun, es wird nicht mehr das Passwort , auch nicht dessen Anfang, im Klartext wiederholt, auch der Admin muss das gar nicht wissen. Es reicht die Kategorisierung in eine Fehlerklasse.

SCOTT fällt jetzt in die Fehlerklasse 4. Ein zu implementierender Automatismus könnte ihn also anmailen, oder der Admin ansprechen und darauf hinweisen: „Lieber User SCOTT, du hast ein unsicheres Passwort. Dieses steht auf der Liste der weltweit am meisten genutzten Passwörter. Bitte denke dir aus Sicherheitsgründen, zu unsere aller Beruhigung, ein neues Passwort aus.„.

Kategorien der Fehlerklassen:

 

  1. Passwort wie username (in Lowercase )
  2. Passwort wie USERNAME  (in Uppercase )
  3. Passwort wie Username  (in Mixedcase (Capitals) )
  4. befindet sich in Hitliste der beliebtesten Passwörter

Hier noch zu guter Letzt das erweiterte Script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
SET serveroutput ON size 1000000

DECLARE
TYPE string_list IS VARRAY(40) OF VARCHAR(16);

pw_list STRING_LIST := string_list(
'123456','password','12345678','qwerty','12345','123456789','football',
'1234','1234567','baseball','welcome','1234567890','abc123','111111',
'1qaz2wsx','dragon','master','monkey','letmein','login','princess',
'qwertyuiop', 'solo', 'passw0rd', 'starwars', 'hallo', 'passwort',
'hallo123', 'schalke04', 'passwort1', 'qwertz', 'schatz', 'hallo1',
'fussball', NULL, NULL, NULL,NULL,NULL,NULL ); -- hier Erweiterungen eintragen

pwstyle PLS_INTEGER;
DUMMY VARCHAR2(80);
c_username VARCHAR2(256);
dbname v$database.name%TYPE;

lv_pwd_raw RAW(128);
lv_enc_raw RAW(2048);
LV_HASH_FOUND VARCHAR2(300);

CURSOR c_main(cp_user IN VARCHAR2)
IS
SELECT SUBSTR(spare4,3,40) hash,
SUBSTR(spare4,43,20) salt,
spare4
FROM sys.USER$
WHERE name=cp_user;
lv_user c_main%ROWTYPE;

CURSOR cu_user
IS
SELECT username
FROM dba_users
WHERE ACCOUNT_STATUS LIKE 'OPEN%';

FUNCTION COMPARE_PW_HASH(luser IN VARCHAR2, lpw IN VARCHAR2)
RETURN NUMBER IS

BEGIN
OPEN c_main(UPPER(luser));
FETCH c_main INTO lv_user;
CLOSE C_MAIN;
lv_pwd_raw := UTL_RAW.cast_to_raw(lpw)||HEXTORAW(lv_user.salt);
lv_enc_raw := sys.dbms_crypto.hash(lv_pwd_raw, 3);
lv_hash_found:=UTL_RAW.cast_to_varchar2(lv_enc_raw);

IF LV_ENC_RAW = LV_USER.hash THEN
-- DBMS_OUTPUT.PUT_LINE('PWD found for User ' || luser );
RETURN 1;
ELSE
RETURN 0;
END IF;
END;

BEGIN
DBMS_OUTPUT.ENABLE(10000000);

SELECT NAME INTO dbname FROM v$database;

OPEN cu_user;
LOOP
FETCH cu_user INTO c_username;
EXIT WHEN cu_user%NOTFOUND;

pwstyle := 0;

IF COMPARE_PW_HASH ( c_username , c_username ) > 0 THEN
pwstyle := 1; -- PW = username
ELSIF COMPARE_PW_HASH ( c_username , UPPER(c_username) ) > 0 THEN
pwstyle := 2; -- PW = UPPER(USERNAME)
ELSIF COMPARE_PW_HASH ( c_username , UPPER(SUBSTR(c_username,1,1))
|| LOWER(SUBSTR(c_username,2,LENGTH(c_username)))
) > 0 THEN
pwstyle := 3; -- PW = Username
ELSE
FOR i IN 1..36 LOOP
IF COMPARE_PW_HASH ( c_username , pw_list(i) ) > 0 THEN
pwstyle := i+3; -- PW ist PW(i) aus Liste der meistgenutzten
EXIT;
END IF;
END LOOP;
END IF;

IF pwstyle > 0 THEN
DBMS_OUTPUT.PUT_LINE( c_username || ' is UNSAFE PWSTYLE IS ' || PWSTYLE || ' **' );
END IF;
END LOOP;
CLOSE cu_user;
--
END;
/
Wissen zum Thema:

 

https://www.experts-exchange.com/articles/855/How-Oracle-Stores-Passwords.html

Tablespace Map (wie in DBA Studio), auch in Oracle Enterprise Manager 12c

Die aus dem älteren DBA Studio bekannte, recht nützliche Tablespace Map ist im Enterprise Manager recht schwer zu finden.

So sah sie aus:

Tablespace Map DBA Studio
Tablespace Map DBA Studio

Hier kurz der Wegweiser zum Ziel, um sie auch im Enterprise Manager zu nutzen:

Wir navigieren unter Administration – Storage zu Tablespaces:

Was man selektieren muss (Click to View, then ESC to go back)

In der Auswahlbox über die Funktionen, die defaultmäßig auf „Add Datafile“ steht, selektieren wir „Show Tablespace Contents„. Ich muss zugeben, darunter hatte ich etwas Anderes erwartet.

Es geht dann eine Ansicht auf, auf der wir ganz unten unter dem „Contents“ einen unscheinbaren Link finden, „Extent Map„.

die Tablespacemap
Die Tablespacemap

Nutzen wir diesen Link, klappt tatsächlich die aus dem DBA Studio bekannte Ansicht auf! Welch Überraschung.

Für diejenigen, die noch nicht wissen, was man damit Nützliches anfangen kann, folgt eine kurze Erklärung.  Es werden genutze und freie Blöcke farblich gekennzeichnet. Man erkennt damit die Nutzung (und Fragmentierung) des Tablespaces.

Tablespacemap mit Legende
Tablespacemap mit Legende

 

Wofür ist das gut?

Manchmal ist ein Tablespace zu groß, und man bekommt ihn mit ALTER .. RESIZE nicht kleiner, da am Ende des Tablespace noch ein Objekt ist.

ORA-03297: file contains used data beyond requested RESIZE value

Welches Objekt da stört, erfährt man hier dynamisch mit einem einfachen Mouseover (Tooltip). Dann kann man dieses Objekt reorganisieren und hat die Chance, dass der Tablespace verkleinert werden kann. Dies ist nützlich bei Objekten, die üblicherweise nicht automatisiert reorganisert werden können wie SYSAUX.

Hier ist rechts unten die hellblaue Fläche das Objekt welches eine Reorganisation verhindert. Als Tabelle ein Move, als Index ein REBUILD löst das Problem.

Natürlich kann man die den Shrink störenden „höchsten“ Objekte auch mittels der Console ermitteln:

1
2
3
4
5
6
7
8
9
10
11
SELECT * FROM
( SELECT file_id, block_id, block_id + blocks - 1 end_block, owner,
segment_name, partition_name, segment_type
FROM dba_extents
WHERE tablespace_name = 'SYSAUX'
UNION ALL
SELECT file_id, block_id, block_id + blocks - 1 end_block,'free' owner, 'free' segment_name,
NULL partition_name, NULL SEGMENT_TYPE FROM DBA_FREE_SPACE
WHERE TABLESPACE_NAME = 'SYSAUX'
ORDER BY 1 DESC, 2 DESC)
WHERE ROWNUM &lt; 10;

Ein beispielhafter Output sieht dann z.B. so aus:

FILE_ID BLOCK_ID END_BLOCK OWNER SEGMENT_NAME PARTITION_NAME SEGMENT_TYPE
———- ———- ———- —— ——————————- —————————— ——————
10 25792 25855 SYS I_WRI$_OPTSTAT_H_ST INDEX
10 25280 25791 SYS AUD$ TABLE
10 24192 24255 SYS I_WRI$_OPTSTAT_H_OBJ#_ICOL#_ST INDEX
10 24128 24191 SYS WRH$_LATCH WRH$_LATCH_489711439_34766 TABLE PARTITION

 

Dies wären dann die Kandidaten für einen MOVE/REBUILD, um den Platz am Ende der Datei freizuschaufeln.