Loading plugin/smb/smb.cpp +6 −0 Changes for plugin/smb/smb.cpp: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -194,6 +194,12 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t return pdu; }; /* verify the signature of a signed request once the session is keyed */ if (c.haveKeys && (req.flags & smb::SMB2_FLAGS_SIGNED)) { if (!smb::verifySignature(c.dialect, c.keys.signing, msg)) return errorResponse(req, smb::STATUS_ACCESS_DENIED); } switch (req.command) { case smb::SMB2_NEGOTIATE: { smb::NegotiateRequest nr; Loading src/rpc/samr.cpp +64 −12 Changes for src/rpc/samr.cpp: 64 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -85,6 +85,36 @@ namespace { return in.unistrBuffer(); } /* SAMR acct_flags (ACB_*) <-> LDAP userAccountControl */ uint32_t acbToUac(uint32_t acb) { uint32_t u = 0; if (acb & 0x0001) u |= 0x0002; /* DISABLED */ if (acb & 0x0002) u |= 0x0008; /* HOMDIRREQ */ if (acb & 0x0004) u |= 0x0020; /* PWNOTREQ */ if (acb & 0x0010) u |= 0x0200; /* NORMAL */ if (acb & 0x0040) u |= 0x0800; /* DOMTRUST */ if (acb & 0x0080) u |= 0x1000; /* WSTRUST */ if (acb & 0x0100) u |= 0x2000; /* SVRTRUST */ if (acb & 0x0200) u |= 0x10000; /* PWNOEXP */ if (acb & 0x0400) u |= 0x0010; /* AUTOLOCK -> LOCKOUT */ if (!u) u = 0x0200; /* default NORMAL */ return u; } uint32_t uacToAcb(uint32_t u) { uint32_t acb = 0; if (u & 0x0002) acb |= 0x0001; if (u & 0x0008) acb |= 0x0002; if (u & 0x0020) acb |= 0x0004; if (u & 0x0200) acb |= 0x0010; if (u & 0x0800) acb |= 0x0040; if (u & 0x1000) acb |= 0x0080; if (u & 0x2000) acb |= 0x0100; if (u & 0x10000) acb |= 0x0200; if (u & 0x0010) acb |= 0x0400; if (!acb) acb |= 0x0010; return acb; } DispatchResult ok(const std::vector<uint8_t> &stub, uint32_t status = ST_OK) { DispatchResult r; r.stub = stub; r.status = status; return r; } Loading Loading @@ -193,8 +223,8 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, NdrPull in(stub); in.handle(); /* domain handle */ std::string acct = readName(in); uint32_t acctFlags = 0x1000; /* default WORKSTATION_TRUST for CreateUser */ if (opnum == SAMR_CREATEUSER2) acctFlags = in.u32(); uint32_t acb = 0x0080; /* default ACB_WSTRUST for CreateUser */ if (opnum == SAMR_CREATEUSER2) acb = in.u32(); in.u32(); /* desired access */ Dom d; if (!openDom(d)) return handleOut(PolicyHandle{}, ST_INVALID); dsdb::Directory dir(*d.bck, d.meta); Loading @@ -205,18 +235,20 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, } std::string shortName = acct; if (!shortName.empty() && shortName.back() == '$') shortName.pop_back(); dsdb::Dn dn = d.meta.baseDn.child("CN", "Computers").child("CN", shortName); /* samr acct_flags are ACB_*; convert to LDAP userAccountControl */ bool computer = (acb & 0x0080) || (acb & 0x0100); /* ACB_WSTRUST/SVRTRUST */ uint32_t uac = acbToUac(acb); dsdb::Dn dn = d.meta.baseDn.child("CN", computer ? "Computers" : "Users").child("CN", shortName); uint32_t rid = dir.allocateRid(); dsdb::AttrMap a; bool computer = (acctFlags & 0x1000) || (acctFlags & 0x2000); /* WS or SRV trust */ if (computer) a["objectClass"] = {"top", "person", "organizationalPerson", "user", "computer"}; else a["objectClass"] = {"top", "person", "organizationalPerson", "user"}; a["sAMAccountName"] = {acct}; a["objectSid"] = {d.info.sid.withRid(rid).binary()}; /* new account is disabled until the password is set */ dsdb::setInt(a, "userAccountControl", int32_t(acctFlags | 0x0002 /*DISABLE*/ | 0x0020 /*PASSWD_NOTREQD*/)); /* new account is disabled + no-pw until the password is set */ dsdb::setInt(a, "userAccountControl", int32_t(uac | 0x0002 /*DISABLE*/ | 0x0020 /*PASSWD_NOTREQD*/)); dsdb::setInt(a, "primaryGroupID", computer ? 515 : 513); uuid::uuid g = dir.create(dn, a); NdrPush p; Loading Loading @@ -246,9 +278,9 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, in.u16(); /* the switch echoes the level */ std::unique_ptr<dsdb::Secrets> newSec; if (level == 16) { /* SAMR_USER_CONTROL_INFORMATION: u32 acct_flags */ uint32_t uac = in.u32(); dsdb::setInt(attrs, "userAccountControl", int32_t(uac)); /* SAMR_USER_CONTROL_INFORMATION: u32 acct_flags (ACB_*) */ uint32_t acb = in.u32(); dsdb::setInt(attrs, "userAccountControl", int32_t(acbToUac(acb))); } else if ((level == 25 || level == 26 || level == 24 || level == 23) && sessionKey.size() >= 16) { /* the buffer is a 516-byte SAMPR_USER_PASSWORD encrypted with * RC4(session key) (levels 24/25) ... level 25/26 include extra Loading Loading @@ -294,9 +326,29 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, return ok(p.buf); } case SAMR_QUERYUSERINFO: { /* minimal: return a null info pointer + success is not valid; * respond with level unsupported for now */ NdrPush p; p.ref(false); p.u32(0xC0000003 /* INVALID_INFO_CLASS-ish */); NdrPull in(stub); PolicyHandle h = in.handle(); uint16_t level = in.u16(); HObj obj; if (!lookupHandle(h, obj) || obj.kind != H_USER) { NdrPush p; p.ref(false); p.u32(ST_ACCESS); return ok(p.buf); } Dom d; if (!openDom(d)) { NdrPush p; p.ref(false); p.u32(ST_INVALID); return ok(p.buf); } dsdb::Directory dir(*d.bck, d.meta); auto e = dir.byGuid(obj.guid); if (!e) { NdrPush p; p.ref(false); p.u32(ST_NO_SUCH_USER); return ok(p.buf); } if (level == 16) { uint32_t uac = uint32_t(dsdb::intValue(e->attrs, "userAccountControl", 0x200)); NdrPush p; p.ref(true); /* info pointer */ p.u16(16); /* union discriminant */ p.align(4); p.u32(uacToAcb(uac)); /* SAMR_USER_CONTROL_INFORMATION.acct_flags */ p.u32(ST_OK); return ok(p.buf); } /* other levels not yet supported */ NdrPush p; p.ref(false); p.u32(0xC0000003 /* INVALID_INFO_CLASS */); return ok(p.buf); } default: Loading tests/proto/test_samr.cpp +13 −2 Changes for tests/proto/test_samr.cpp: 13 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ TEST(samr_join_flow) { cu.u16(uint16_t(crypto::utf16le("WS1$").size())); cu.ref(true); cu.align(4); cu.u32(4); cu.u32(0); cu.u32(4); { auto u = crypto::utf16le("WS1$"); cu.bytes(u.data(), u.size()); } cu.u32(0x1000); /* acct_flags WORKSTATION_TRUST */ cu.u32(0x0080); /* ACB_WSTRUST */ cu.u32(0x02000000); /* desired access */ auto cur = samrDispatch(50, cu.buf); NdrPull cp(cur.stub); Loading Loading @@ -100,7 +100,18 @@ TEST(samr_join_flow) { NdrPull sup(sur.stub); CHECK_EQ(sup.u32(), uint32_t(0)); samrDispatch(1, ({ NdrPush c; c.handle(userH); c.buf; })); /* QueryUserInfo level 16 -> acct_flags back in ACB form (ACB_WSTRUST set) */ NdrPush qi; qi.handle(userH); qi.u16(16); auto qir = samrDispatch(36, qi.buf); NdrPull qp(qir.stub); CHECK(qp.ref() != 0); CHECK_EQ(qp.u16(), uint16_t(16)); qp.align(4); uint32_t acb = qp.u32(); CHECK(acb & 0x0080); /* ACB_WSTRUST */ CHECK_EQ(qp.u32(), uint32_t(0)); { NdrPush cl; cl.handle(userH); samrDispatch(1, cl.buf); } unlink(path.c_str()); unlink((path + ".lock").c_str()); unlink((path + ".dom").c_str()); } Loading Loading
plugin/smb/smb.cpp +6 −0 Changes for plugin/smb/smb.cpp: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -194,6 +194,12 @@ std::vector<uint8_t> SmbServer::handleMessage(Conn &c, const std::vector<uint8_t return pdu; }; /* verify the signature of a signed request once the session is keyed */ if (c.haveKeys && (req.flags & smb::SMB2_FLAGS_SIGNED)) { if (!smb::verifySignature(c.dialect, c.keys.signing, msg)) return errorResponse(req, smb::STATUS_ACCESS_DENIED); } switch (req.command) { case smb::SMB2_NEGOTIATE: { smb::NegotiateRequest nr; Loading
src/rpc/samr.cpp +64 −12 Changes for src/rpc/samr.cpp: 64 added lines, 12 removed lines. Original line number Diff line number Diff line Loading @@ -85,6 +85,36 @@ namespace { return in.unistrBuffer(); } /* SAMR acct_flags (ACB_*) <-> LDAP userAccountControl */ uint32_t acbToUac(uint32_t acb) { uint32_t u = 0; if (acb & 0x0001) u |= 0x0002; /* DISABLED */ if (acb & 0x0002) u |= 0x0008; /* HOMDIRREQ */ if (acb & 0x0004) u |= 0x0020; /* PWNOTREQ */ if (acb & 0x0010) u |= 0x0200; /* NORMAL */ if (acb & 0x0040) u |= 0x0800; /* DOMTRUST */ if (acb & 0x0080) u |= 0x1000; /* WSTRUST */ if (acb & 0x0100) u |= 0x2000; /* SVRTRUST */ if (acb & 0x0200) u |= 0x10000; /* PWNOEXP */ if (acb & 0x0400) u |= 0x0010; /* AUTOLOCK -> LOCKOUT */ if (!u) u = 0x0200; /* default NORMAL */ return u; } uint32_t uacToAcb(uint32_t u) { uint32_t acb = 0; if (u & 0x0002) acb |= 0x0001; if (u & 0x0008) acb |= 0x0002; if (u & 0x0020) acb |= 0x0004; if (u & 0x0200) acb |= 0x0010; if (u & 0x0800) acb |= 0x0040; if (u & 0x1000) acb |= 0x0080; if (u & 0x2000) acb |= 0x0100; if (u & 0x10000) acb |= 0x0200; if (u & 0x0010) acb |= 0x0400; if (!acb) acb |= 0x0010; return acb; } DispatchResult ok(const std::vector<uint8_t> &stub, uint32_t status = ST_OK) { DispatchResult r; r.stub = stub; r.status = status; return r; } Loading Loading @@ -193,8 +223,8 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, NdrPull in(stub); in.handle(); /* domain handle */ std::string acct = readName(in); uint32_t acctFlags = 0x1000; /* default WORKSTATION_TRUST for CreateUser */ if (opnum == SAMR_CREATEUSER2) acctFlags = in.u32(); uint32_t acb = 0x0080; /* default ACB_WSTRUST for CreateUser */ if (opnum == SAMR_CREATEUSER2) acb = in.u32(); in.u32(); /* desired access */ Dom d; if (!openDom(d)) return handleOut(PolicyHandle{}, ST_INVALID); dsdb::Directory dir(*d.bck, d.meta); Loading @@ -205,18 +235,20 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, } std::string shortName = acct; if (!shortName.empty() && shortName.back() == '$') shortName.pop_back(); dsdb::Dn dn = d.meta.baseDn.child("CN", "Computers").child("CN", shortName); /* samr acct_flags are ACB_*; convert to LDAP userAccountControl */ bool computer = (acb & 0x0080) || (acb & 0x0100); /* ACB_WSTRUST/SVRTRUST */ uint32_t uac = acbToUac(acb); dsdb::Dn dn = d.meta.baseDn.child("CN", computer ? "Computers" : "Users").child("CN", shortName); uint32_t rid = dir.allocateRid(); dsdb::AttrMap a; bool computer = (acctFlags & 0x1000) || (acctFlags & 0x2000); /* WS or SRV trust */ if (computer) a["objectClass"] = {"top", "person", "organizationalPerson", "user", "computer"}; else a["objectClass"] = {"top", "person", "organizationalPerson", "user"}; a["sAMAccountName"] = {acct}; a["objectSid"] = {d.info.sid.withRid(rid).binary()}; /* new account is disabled until the password is set */ dsdb::setInt(a, "userAccountControl", int32_t(acctFlags | 0x0002 /*DISABLE*/ | 0x0020 /*PASSWD_NOTREQD*/)); /* new account is disabled + no-pw until the password is set */ dsdb::setInt(a, "userAccountControl", int32_t(uac | 0x0002 /*DISABLE*/ | 0x0020 /*PASSWD_NOTREQD*/)); dsdb::setInt(a, "primaryGroupID", computer ? 515 : 513); uuid::uuid g = dir.create(dn, a); NdrPush p; Loading Loading @@ -246,9 +278,9 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, in.u16(); /* the switch echoes the level */ std::unique_ptr<dsdb::Secrets> newSec; if (level == 16) { /* SAMR_USER_CONTROL_INFORMATION: u32 acct_flags */ uint32_t uac = in.u32(); dsdb::setInt(attrs, "userAccountControl", int32_t(uac)); /* SAMR_USER_CONTROL_INFORMATION: u32 acct_flags (ACB_*) */ uint32_t acb = in.u32(); dsdb::setInt(attrs, "userAccountControl", int32_t(acbToUac(acb))); } else if ((level == 25 || level == 26 || level == 24 || level == 23) && sessionKey.size() >= 16) { /* the buffer is a 516-byte SAMPR_USER_PASSWORD encrypted with * RC4(session key) (levels 24/25) ... level 25/26 include extra Loading Loading @@ -294,9 +326,29 @@ DispatchResult samrDispatch(uint16_t opnum, const std::vector<uint8_t> &stub, return ok(p.buf); } case SAMR_QUERYUSERINFO: { /* minimal: return a null info pointer + success is not valid; * respond with level unsupported for now */ NdrPush p; p.ref(false); p.u32(0xC0000003 /* INVALID_INFO_CLASS-ish */); NdrPull in(stub); PolicyHandle h = in.handle(); uint16_t level = in.u16(); HObj obj; if (!lookupHandle(h, obj) || obj.kind != H_USER) { NdrPush p; p.ref(false); p.u32(ST_ACCESS); return ok(p.buf); } Dom d; if (!openDom(d)) { NdrPush p; p.ref(false); p.u32(ST_INVALID); return ok(p.buf); } dsdb::Directory dir(*d.bck, d.meta); auto e = dir.byGuid(obj.guid); if (!e) { NdrPush p; p.ref(false); p.u32(ST_NO_SUCH_USER); return ok(p.buf); } if (level == 16) { uint32_t uac = uint32_t(dsdb::intValue(e->attrs, "userAccountControl", 0x200)); NdrPush p; p.ref(true); /* info pointer */ p.u16(16); /* union discriminant */ p.align(4); p.u32(uacToAcb(uac)); /* SAMR_USER_CONTROL_INFORMATION.acct_flags */ p.u32(ST_OK); return ok(p.buf); } /* other levels not yet supported */ NdrPush p; p.ref(false); p.u32(0xC0000003 /* INVALID_INFO_CLASS */); return ok(p.buf); } default: Loading
tests/proto/test_samr.cpp +13 −2 Changes for tests/proto/test_samr.cpp: 13 added lines, 2 removed lines. Original line number Diff line number Diff line Loading @@ -69,7 +69,7 @@ TEST(samr_join_flow) { cu.u16(uint16_t(crypto::utf16le("WS1$").size())); cu.ref(true); cu.align(4); cu.u32(4); cu.u32(0); cu.u32(4); { auto u = crypto::utf16le("WS1$"); cu.bytes(u.data(), u.size()); } cu.u32(0x1000); /* acct_flags WORKSTATION_TRUST */ cu.u32(0x0080); /* ACB_WSTRUST */ cu.u32(0x02000000); /* desired access */ auto cur = samrDispatch(50, cu.buf); NdrPull cp(cur.stub); Loading Loading @@ -100,7 +100,18 @@ TEST(samr_join_flow) { NdrPull sup(sur.stub); CHECK_EQ(sup.u32(), uint32_t(0)); samrDispatch(1, ({ NdrPush c; c.handle(userH); c.buf; })); /* QueryUserInfo level 16 -> acct_flags back in ACB form (ACB_WSTRUST set) */ NdrPush qi; qi.handle(userH); qi.u16(16); auto qir = samrDispatch(36, qi.buf); NdrPull qp(qir.stub); CHECK(qp.ref() != 0); CHECK_EQ(qp.u16(), uint16_t(16)); qp.align(4); uint32_t acb = qp.u32(); CHECK(acb & 0x0080); /* ACB_WSTRUST */ CHECK_EQ(qp.u32(), uint32_t(0)); { NdrPush cl; cl.handle(userH); samrDispatch(1, cl.buf); } unlink(path.c_str()); unlink((path + ".lock").c_str()); unlink((path + ".dom").c_str()); } Loading